jim stafford
The student will learn:
foundational build concepts for simple, pure-Java solution
At the conclusion of this lecture and related exercises, the student will be able to:
create source code for an executable Java class
add that Java class to a Maven module
build the module using a Maven pom.xml
execute the application using a classpath
configure the application as an executable JAR
execute an application packaged as an executable JAR
package info.ejava.examples.app.build.javamain;
import java.util.List;
public class SimpleMainApp { (1)
public static void main(String...args) { (2) (3)
System.out.println("Hello " + List.of(args));
}
}
1 | public class |
2 | implements a static main() method |
3 | optionally accepts arguments |
|-- pom.xml (1)
`-- src
|-- main (2)
| |-- java
| | `-- info
| | `-- ejava
| | `-- examples
| | `-- app
| | `-- build
| | `-- javamain
| | `-- SimpleMainApp.java
| `-- resources (3)
`-- test (4)
|-- java
`-- resources
1 | pom.xml will define our project artifact and how to build it |
2 | src/main will contain the pre-built, source form of our artifacts that will be part of our primary JAR output for the module |
3 | src/main/resources is commonly used for property files or other resource files
read in during the program execution |
4 | src/test is will contain the pre-built, source form of our test artifacts. These will not be part of the
primary JAR output for the module |
Limited to:
Compiling Java class
Packaging .class into a standard Java JAR
groupId
a hierarchical name representing one or more artifacts
artifactId
primary/base name for the artifact in thie module
should be the same as the directory name
version
identifies version of built module
SNAPSHOT
represents a version in progress. Timestamps will be used behind the scenes to attempt to stay current.
non-SNAPSHOT releases are to be immutable. Once they are downloaded/present, updates are never attempted.
packaging
activates a Maven plugins according to a pre-defined profile
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>info.ejava.examples.app</groupId> (1)
<artifactId>java-app-example</artifactId> (2)
<version>6.1.0-SNAPSHOT</version> (3)
<packaging>jar</packaging> (4)
<project>
name
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>info.ejava.examples.app</groupId>
<artifactId>java-app-example</artifactId>
<version>6.1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>App::Build::Java Main Example</name> (1)
<project>
1 | name appears in Maven build output but not required |
Make build more deterministic in multiple environments
Each version of Maven has a set of default plugins and plugin versions
Each plugin version may or may not have a set of defaults (e.g., not Java 17) that are compatible with our module
<properties>
<java.target.version>17</java.target.version>
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
</properties>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<release>${java.target.version}</release>
</configuration>
</plugin>
</plugins>
</pluginManagement>
Use pluginManagement
to define a plugin if it activated in the module build
useful to promote consistency in multi-module builds
commonly seen in parent modules
Use plugins
to declare that a plugin be active in the module build
ideally only used by child modules
our child module indirectly activated several plugins by using the jar
packaging type
Maven modules are commonly built with the following commands/ phases
clean
removes previously built artifacts
package
creates primary artifact(s) (e.g., JAR)
processes main and test resources
compiles main and test classes
runs unit tests
builds the archive
[INFO] Scanning for projects...
[INFO]
[INFO] --------------< info.ejava.examples.app:java-app-example >--------------
[INFO] Building App::Build::Java App Example 6.1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.4.0:clean (default-clean) @ java-app-example ---
[INFO] Deleting .../java-app-example/target
[INFO]
[INFO] --- maven-resources-plugin:3.3.1:resources (default-resources) @ java-app-example ---
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO]
[INFO] --- maven-compiler-plugin:3.13.0:compile (default-compile) @ java-app-example ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 1 source file with javac [debug parameters release 17] to target/classes
[INFO]
[INFO] --- maven-resources-plugin:3.3.1:testResources (default-testResources) @ java-app-example ---
[INFO] Copying 0 resource from src/test/resources to target/test-classes
[INFO]
[INFO] --- maven-compiler-plugin:3.13.0:testCompile (default-testCompile) @ java-app-example ---
[INFO] Recompiling the module because of changed dependency.
[INFO]
[INFO] --- maven-surefire-plugin:3.3.1:test (default-test) @ java-app-example ---
[INFO]
[INFO] --- maven-jar-plugin:3.4.2:jar (default-jar) @ java-app-example ---
[INFO] Building jar: .../java-app-example/target/java-app-example-6.1.0-SNAPSHOT.jar
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.783 s
mvn clean package
product
|-- pom.xml
|-- src
`-- target
|-- classes (1)
| `-- info
| `-- ejava
| `-- examples
| `-- app
| `-- build
| `-- javamain
| `-- SimpleMainApp.class
...
|-- java-app-example-6.1.0-SNAPSHOT.jar (2)
...
`-- test-classes (3)
1 | target/classes for built artifacts from src/main |
2 | primary artifact(s) (e.g., Java Archive (JAR)) |
3 | target/test-classes for built artifacts from src/test |
Key MANIFEST.MF artifacts:
SimpleMainApp.class
is the compiled version of our application
[META-INF/MANIFEST.MF](https://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html) contains properties relevant to the archive
$ jar tf target/java-app-example-*-SNAPSHOT.jar | egrep -v "/$" | sort
META-INF/MANIFEST.MF
META-INF/maven/info.ejava.examples.app/java-app-example/pom.properties
META-INF/maven/info.ejava.examples.app/java-app-example/pom.xml
info/ejava/examples/app/build/javamain/SimpleMainApp.class
|
Using the fully qualified classname of the class that contains our main() method
$ java -cp target/java-app-example-*-SNAPSHOT.jar info.ejava.examples.app.build.javamain.SimpleMainApp
Output:
Hello []
$ java -cp target/java-app-example-*-SNAPSHOT.jar info.ejava.examples.app.build.javamain.SimpleMainApp arg1 arg2 "arg3 and 4"
Output:
Hello [arg1, arg2, arg3 and 4]
Conceptually simple with single entry point
All options documented at MANIFEST.MF properties
$ unzip -qc target/java-app-example-*-SNAPSHOT.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.4.2
Build-Jdk-Spec: 17
Main-Class: info.ejava.examples.app.build.javamain.SimpleMainApp
Can surgically add that property using maven-jar-plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<configuration>
<archive>
<manifest>
<mainClass>info.ejava.examples.app.build.javamain.SimpleMainApp</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
Specifying just the JAR within our MANIFEST.MF pointing at our class with the main() method
$ java -jar target/java-app-example-*-SNAPSHOT.jar
Output:
Hello []
$ java -jar target/java-app-example-*-SNAPSHOT.jar one two "three and four"
Output:
Hello [one, two, three and four]
<build>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId> (1)
<executions>
<execution>
<id>execute-jar</id>
<phase>integration-test</phase> (4)
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<java fork="true" classname="info.ejava.examples.app.build.javamain.SimpleMainApp"> (2)
<classpath>
<pathelement path="${project.build.directory}/${project.build.finalName}.jar"/>
</classpath>
<arg value="Ant-supplied java -cp"/>
<arg value="Command Line"/>
<arg value="args"/>
</java>
<java fork="true"
jar="${project.build.directory}/${project.build.finalName}.jar"> (3)
<arg value="Ant-supplied java -jar"/>
<arg value="Command Line"/>
<arg value="args"/>
</java>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
1 | Using the maven-ant-run plugin to execute Ant task |
2 | Using the java Ant task to execute shell java -cp command line |
3 | Using the java Ant task to execute shell java -jar command line |
4 | Running the plugin during the integration-phase |
$ mvn clean verify
[INFO] Scanning for projects...
[INFO]
[INFO] -------------< info.ejava.examples.app:java-app-example >--------------
...
[INFO] --- maven-jar-plugin:3.2.2:jar (default-jar) @ java-app-example -(1)
[INFO] Building jar: .../java-app-example/target/java-app-example-6.1.0-SNAPSHOT.jar
[INFO]
...
[INFO] --- maven-antrun-plugin:3.1.0:run (execute-jar) @ java-app-example ---
[INFO] Executing tasks (2)
[INFO] [java] Hello [Ant-supplied java -cp, Command Line, args]
[INFO] [java] Hello [Ant-supplied java -jar, Command Line, args]
[INFO] Executed tasks
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] --- maven-jar-plugin:3.4.2:jar (default-jar) @ java-app-example -(1)
[INFO] Building jar: .../java-app-example/target/java-app-example-6.1.0-SNAPSHOT.jar
[INFO]
...
[INFO] --- maven-antrun-plugin:3.1.0:run (execute-jar) @ java-app-example ---
[INFO] Executing tasks (2)
[INFO] [java] Hello [Ant-supplied java -cp, Command Line, args]
[INFO] [java] Hello [Ant-supplied java -jar, Command Line, args]
[INFO] Executed tasks
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
1 | Our plugin is executing |
2 | Our application was executed and the results displayed |
The JVM will execute the static main()
method of the class specified in the java command
The class must be in the JVM classpath
Maven can be used to build a JAR with classes
A JAR can be the subject of a java execution
The Java META-INF/MANIFEST.MF
Main-Class
property within the target JAR can express the class with the main()
method to execute
The maven-jar-plugin can be used to add properties to the META-INF/MANIFEST.MF
file
A Maven build can be configured to execute a JAR