Pure Java Main Application

jim stafford

Introduction

Goals

The student will learn:

  • foundational build concepts for simple, pure-Java solution

Objectives

At the conclusion of this lecture and related exercises, the student will be able to:

  1. create source code for an executable Java class

  2. add that Java class to a Maven module

  3. build the module using a Maven pom.xml

  4. execute the application using a classpath

  5. configure the application as an executable JAR

  6. execute an application packaged as an executable JAR

Simple Java Class with a Main

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));
    }
}
1public class
2implements a static main() method
3optionally accepts arguments

Project Source Tree

|-- pom.xml (1)
`-- src
    |-- main (2)
    |   |-- java
    |   |   `-- info
    |   |       `-- ejava
    |   |           `-- examples
    |   |               `-- app
    |   |                   `-- build
    |   |                       `-- javamain
    |   |                           `-- SimpleMainApp.java
    |   `-- resources (3)
    `-- test (4)
        |-- java
        `-- resources
1pom.xml will define our project artifact and how to build it
2src/main will contain the pre-built, source form of our artifacts that will be part of our primary JAR output for the module
3src/main/resources is commonly used for property files or other resource files read in during the program execution
4src/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

Building the Java Archive (JAR) with Maven

Limited to:

  • Compiling Java class

  • Packaging .class into a standard Java JAR

Add Core pom.xml Document

  1. groupId a hierarchical name representing one or more artifacts

  2. artifactId primary/base name for the artifact in thie module

    • should be the same as the directory name

  3. 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.

  4. 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>

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>
1name appears in Maven build output but not required

Define Plugin Versions

  • 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>

pluginManagement vs. plugins

  • 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

Build the Module

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

Project Build Tree

  • 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)
1target/classes for built artifacts from src/main
2primary artifact(s) (e.g., Java Archive (JAR))
3target/test-classes for built artifacts from src/test

Resulting Java Archive (JAR)

Key MANIFEST.MF artifacts:

$ 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
  • jar tf lists the contents of the JAR

  • egrep is being used to exclude non-files (i.e., directores) that end with "/"

  • sort performs an ordering of the output

  • | pipe character sends the stdout of previous command to the stdin of the next command

Execute the Application

Using the fully qualified classname of the class that contains our main() method

Example with no arguments
$ java -cp target/java-app-example-*-SNAPSHOT.jar info.ejava.examples.app.build.javamain.SimpleMainApp

Output:
Hello []
Example with arguments
$ 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]

Configure Application as an Executable JAR

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

Automate Additions to MANIFEST.MF using Maven

    <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>

Execute the JAR versus just adding to classpath

Specifying just the JAR within our MANIFEST.MF pointing at our class with the main() method

Example with no arguments
$ java -jar target/java-app-example-*-SNAPSHOT.jar

Output:
Hello []
Example with arguments
$ java -jar target/java-app-example-*-SNAPSHOT.jar one two "three and four"

Output:
Hello [one, two, three and four]

Configure pom.xml to Test

<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>
1Using the maven-ant-run plugin to execute Ant task
2Using the java Ant task to execute shell java -cp command line
3Using the java Ant task to execute shell java -jar command line
4Running the plugin during the integration-phase

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
1Our plugin is executing
2Our application was executed and the results displayed

Summary

  1. The JVM will execute the static main() method of the class specified in the java command

  2. The class must be in the JVM classpath

  3. Maven can be used to build a JAR with classes

  4. A JAR can be the subject of a java execution

  5. The Java META-INF/MANIFEST.MF Main-Class property within the target JAR can express the class with the main() method to execute

  6. The maven-jar-plugin can be used to add properties to the META-INF/MANIFEST.MF file

  7. A Maven build can be configured to execute a JAR