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
- Download from the offical site
- Official JavaDocs
- Java Tutorials: docs.oracle.com
- Java Koans to learn the language a single test at a time
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:
main
for the production code.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
- main
- 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.