Java Gradle 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.

Gradle is not just a tool for build or dependency management, it supports the whole life-cycle for Java projects. That includes compiling, testing, packaging, releasing and deploying a project. Gradle can be expanded with custom tasks for specific needs of the project. You can find out more about writing tasks in the official documentation

Dependency Management

Java dependencies are called jars. There multiple established tools (Maven, Ivy, Gradle, sbt) to declare and manage Java dependencies. These tend to come bundled with some kind of task execution framework. In this guide we will focus on Gradle to maintain our Java projects, though we will lean heavily on standard and practices inspired by Maven.

Start out by creating an initial scaffolding with gradle init --type java-application. This will create the basic files and folder that form the core structuer of a Gradle application. For the time being, lets focus on the build.gralde file. It defines our project, its dependencies, and any tasks that we’d need to run.

The inital setup has created a minimal skeleton of a build.gradle file (comments ommited):

// Plugins are a way to share common configuration settings between projects
// Apply the java plugin to add basic support for Java, while the `application`
// adds some basic tasks to package up our application when its ready.
apply plugin: 'java'
apply plugin: 'application'

// Where to get dependencies from. JCenter is the largest repository of jars and very reputable
repositories {
  jcenter()
}

// This is where we will declare our dependnecies
dependencies {
    testCompile 'junit:junit:4.12'
}

// Define the main class for the application, which is part of the `application` plugin
mainClassName = 'App'

Let us focus on the dependencies block to learn how to maintain our project dependencies. Dependening on whether you need the dependency as part of your production code or only as part of the testing infrastructue you’ll need to declare you dependency as part of the compile configuration or the testCompile configuration. In the above example, the JUnit jar has been added to to the testCompile configuration as there is no use for it as part of the finished application. The string in single-quotes represents the Maven Artifact notation, namely the groupId, the artifactId, and the version. This allows Gradle to pinpoint the depedency, locate it in the repository, and download it wiht its dependencies.

If we wanted to add the Apache Commons Language utilities in version 3.6 we would write the following:

...
dependencies {
    compile 'org.apache.commons:commons-lang3:3.6'
    testCompile 'junit:junit:4.12'
}
...

There are more configurations such as runtime and testRuntime that are used for specialised purposes. See the Gradle Documentation for more details.

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 Gradle project using the maven plugin 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
  • build.gradle

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-gradle-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 jar task of gradle

gradle jar

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 ./build/libs/java-gradle-command-line.jar

Running the Tests

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

$: gradle test
BUILD SUCCESSFUL in 0s
3 actionable tasks: 3 up-to-date

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.