Java Maven Command Line Application

Java is a general-purpose programming language introduced little over 20 years ago. It has become widely popular in the enterprise space due to the Java Virtual Machine, the Garbage Collector, a similar programming model to C/C++ and the promise of easier-to-write programs than its competitors of the time.

It is statically typed, notorious for a proliferation of null, and relies on polymorphism for clever solutions to problems. Since Java 8, more and more functional programming elements have found their way into the language, such as Stream and Optional<T>, making a resurgence since the more traditional Java 6 and 7 releases.

Further Material

Topics, Tools and Terms

Java packages are distributed in jars (from Java archive). The central repository to download these jars from is Maven central.

The Java Virtual Machine (short: JVM) is the platform all Java programs run on. It allows Java code to be operating system agnostic, meaning that a Java program written on MacOS can be compiled and run on Windows without modification.

Maven is not just a tool for build or dependency management, it supports the whole lifecycle for Java projects. That includes compiling, testing, packaging, releasing and deploying of a project (amongst many other more). More about Maven’s build lifecycle can be found on the official documentation.

Dependency Management

One part Maven helps with is managing the dependencies of a project. The central configuration file for that is called pom.xml (POM stands for Project Object Model) and it defines metadata, structure and dependencies of a project. It’s content is written in XML and this is what a basic pom.xml looks like:

<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>guide.vanilla_project</groupId>
  <artifactId>java-maven-command-line</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>Java Maven Command Line Application</name>
  <url>https://vanilla-project.guide/java/maven-command-line</url>

  <dependencies>
    <dependency>
      <groupId>org.junit.vintage</groupId>
      <artifactId>junit-vintage-engine</artifactId>
      <version>4.12.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Let’s go through that file line by line: The modelVersion tells Maven about the POM version itself. It defines what tags can be used for example. The more interesting settings are the ones that come after that.

Java packages are primarily identified by the groupId and artifactId. Where groupId is an identifier for the author of that jar. A convention for that is to use a top-level domain in reverse order. This would be com.google for jars from Google or org.apache.maven for Maven itself. The artifactId then identifies the application/library inside the namespace of groupId.

The setting for version defines, as the name implies, the particular version of a jar file. In our example you there’s a “SNAPSHOT” qualifier that declares a development, or “as-yet-unreleased” version. Oracle’s documentation has more information about Maven version numbers.

For name we can choose whatever description we want to name our project. Last but not least url can define a URL of the project itself.

After that comes the list of dependencies. In the exampe we declare JUnit as a dependency for the project. The <scope>test</scope> tag will make sure that JUnit will only be part of the program during testing, not when it is packaged up for production use.

Version Managers

Version managers allow us to quickly switch between different language versions. There’s no support to automatically install new versions, though. With Java this is a manual process.

Testing Tools

The standard testing framework is called JUnit. JUnit is primarily a runner for our tests, and it comes with it’s own matchers like assertEquals, assertTrue, etc. There are alternatives to that, which provide a more fluent way to express expectations of a test.

One other matcher library is Hamcrest. It supports exepectations in the style of

assertThat(a, is(equalTo(b)));

compared to the more traditional JUnit style

assertEquals(a, b);

Another one is AssertJ, which allows expectations in the style of

assertThat(a).isEqualTo(b);

JUnit itself comes with Hamcrest bundled automatically, so in this guide and the example application we will be using Hamcrest style assertions.

Directory Structure

The directory structure for a maven project consists of a src directory that has two sub-directories:

  1. main for the production code.
  2. test for the tests.

Inside each of those it’s possible to have a resources directory to contain general assets for the project like images or configuration files.

We provided a working example of a minimal project on Github.

  • src
    • main
      • java
    • test
      • java
  • pom.xml

Every subdirectory inside each java directory forms a package. Packages are Java’s way to give classes that belong together a namespace.

Naming Conventions

Package names are in lower case.

File names of classes reflect the class name including the capital first letter. For example the class Vanilla needs to be contained in file Vanilla.java.

Tests match their production code file names with a Test suffix, e.g. tests for code in Vanilla.java should be written in VanillaTest.java.

They also match their production package, so that class com.vanilla_project.Vanilla will have its tests in com.vanilla_project.VanillaTest. While the package names match, the physical location does not (as they’re separated by src/main and src/test).

Example Project

The repository for the example application is available at github.com/vanilla-project/java-maven-command-line.

The main application consists of basically three files:

  • Main.java is the main executable that instantiates and runs:
    • App.java contains the main application that uses:
      • Example.java which contains only one method that returns a string.

Running the Application

To run the application we need to build it first.

This can be done by executing the package target of maven:

mvn package

This will download all dependencies, compile the code, run the tests and package it up into a .jar file.

We can then execute the jar file to run our application:

java -jar target/java-maven-command-line-1.0-SNAPSHOT.jar

Running the Tests

To run the tests we execute mvn test which then looks for all files inside directory src/test and runs them. The output should look like the following:

$: mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building java-maven-command-line 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ java-maven-command-line ---
[INFO] skip non existing resourceDirectory /Users/christoph/development/java-maven-command-line/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ java-maven-command-line ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ java-maven-command-line ---
[INFO] skip non existing resourceDirectory /Users/christoph/development/java-maven-command-line/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ java-maven-command-line ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ java-maven-command-line ---
[INFO] Surefire report directory: /Users/christoph/development/java-maven-command-line/target/surefire-reports

-------------------------------------------------------
 T E S T S
 -------------------------------------------------------
 Running guide.vanilla_project.AppTest
 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.095 sec
 Running guide.vanilla_project.ExampleTest
 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec

 Results :

 Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

 [INFO] ------------------------------------------------------------------------
 [INFO] BUILD SUCCESS
 [INFO] ------------------------------------------------------------------------
 [INFO] Total time: 1.969 s
 [INFO] Finished at: 2017-10-15T13:18:37+01:00
 [INFO] Final Memory: 9M/245M
 [INFO] ------------------------------------------------------------------------

Testing Approach

The test for class Example is only verifying the return value of one method.

App on the other hand is tested via a test-double that gets injected. This allows us to spy on the output of it. We want to avoid printing anything to the screen while running the tests. Injecting a test double in this instance is a nice way to isolate our application from the command line.

In the actual Main class we then inject System.out, which is Java’s standard output stream.