1. Introduction
This material provides an introduction to building a bare bones Java application using a single, simple Java class, packaging that in a Java ARchive (JAR), and executing it two ways:
-
as a class in the classpath
-
as the Main-Class of a JAR
1.2. Objectives
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
2. Simple Java Class with a Main
Our simple Java application starts with a public class with a static main() method that optionally accepts command-line arguments from the caller
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 |
3. Project Source Tree
This class is placed within a module source tree in the
src/main/java directory below a set of additional directories (info/ejava/examples/app/build/javamain)
that match the Java package name of the class (info.ejava.examples.app.build.javamain)
|-- 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 |
4. Building the Java Archive (JAR) with Maven
In setting up the build within Maven, I am going to limit the focus to just compiling our simple Java class and packaging that into a standard Java JAR.
4.1. Add Core pom.xml Document
Add the core document with required GAV information (groupId, artifactId, version) to the pom.xml
file at the root of the module tree. Packaging is also required but will have a default of jar if not supplied.
<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>
| 1 | groupId |
| 2 | artifactId |
| 3 | version |
| 4 | packaging |
|
Module directory should be the same name/spelling as artifactId to align with default directory naming patterns used by plugins. |
|
Packaging specification is optional in this case. The default packaging is |
4.2. Add Optional Elements to pom.xml
-
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 |
4.3. Define Plugin Versions
Define plugin versions so the module can be deterministically built 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>
The jar packaging will automatically activate the maven-compiler-plugin and maven-jar-plugin.
Our definition above identifies the version of the plugin to be used (if used) and any desired
configuration of the plugin(s).
4.4. pluginManagement vs. plugins
-
Use
pluginManagementto define a plugin if it activated in the module build-
useful to promote consistency in multi-module builds
-
commonly seen in parent modules
-
-
Use
pluginsto 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
jarpackaging type
-
5. Build the Module
Maven modules are commonly built with the following commands/ phases
-
cleanremoves previously built artifacts -
packagecreates 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
6. Project Build Tree
The produced build tree from mvn clean package contains the following key artifacts (and more)
|-- 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 |
7. Resulting Java Archive (JAR)
Maven adds a few extra files to the META-INF directory that we can ignore. The key files we want to focus on are:
-
SimpleMainApp.classis 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
|
8. Execute the Application
The application is executed by
-
invoking the
javacommand -
adding the JAR file (and any other dependencies) to the classpath
-
specifying the fully qualified class name 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]
-
example passed three (3) arguments separated by spaces
-
third argument (
arg3 and arg4) used quotes around the entire string to escape spaces and have them included in the single parameter
-
9. Configure Application as an Executable JAR
To execute a specific Java class within a classpath is conceptually simple. However, there is a lot more to know than we need to when there may be only a single entry point. In the following sections we will assign a default Main-Class by using the MANIFEST.MF properties
9.1. Add Main-Class property to MANIFEST.MF
$ 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
9.2. Automate Additions to MANIFEST.MF using Maven
One way to surgically add that property is thru the 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>
|
This is a very specific plugin configuration that would only apply to a specific child module.
Therefore, we would place this in a |
10. Execute the JAR versus just adding to classpath
The executable JAR is executed by
-
invoking the
javacommand -
adding the -jar option
-
adding the JAR file (and any other dependencies) to the classpath
$ 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]
-
example passed three (3) arguments separated by spaces
-
third argument (
three and four) used quotes around the entire string to escape spaces and have them included in the single parameter
-
11. Configure pom.xml to Test
At this point we are ready to create an automated execution of our JAR as a part of the build.
We have to do that after the packaging phase and will leverage the integration-test
Maven phase
<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
|
11.1. Execute JAR as part of the build
$ 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 |
12. Summary
-
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.MFMain-Classproperty within the target JAR can express the class with themain()method to execute -
The maven-jar-plugin can be used to add properties to the
META-INF/MANIFEST.MFfile -
A Maven build can be configured to execute a JAR