2014-07-28

Welcome to part 2 of the Gradle Tutorial. This part builds on Part 1, where we looked at installing Gradle and learnt a few basic commands.

In this part of the tutorial, we shall look at how you can use Gradle to compile/build/test your Java projects. The focus will be more on Gradle mechanics rather Java code, so I will be using simple Java projects that help illustrate the Gradle concept. You should be able to build on this while tackling a larger Java code base.

This part assumes that you have a Gradle installation on your machine and the basic environment is setup. For more information on that, refer to Part 1.

Gradle : Project , Tasks and Plugins

Let us recap a few things from the previous session and add to it some more stuff that will be required in this tutorial.

To summarize, the build.gradle was the file that we wrote to drive our Gradle build process and the build file contained tasks i.e. instructions to do something. Think of tasks as the ones that we would normally do if we did not have any of these tools to manage the build process. These tasks would be compile, test, build the jar, deploy, etc.

We do not have to write all these tasks by hand and just like what we have seen with other build tools and our IDEs, plugins come to our rescue. There are several Gradle plugins that make it easier for us to simply invoke the Tasks and if we follow convention, then Gradle does the rest for us.

What do we mean by a Plugin ? A plugin is a mechanism by which we can extend the capabilities of Gradle. Gradle need to know how to build everything and anything for us. But we can make that possible if we know a certain process and can create pre-built tasks that it can then perform for us. This is the role of a plugin.

The series will not focus on how to write a Gradle plugin. Rather we will use great Gradle plugins that are available to us to do our job. These plugins will add tasks that we can invoke straightaway. And you know what a Task is .. correct ? We saw that in the first part.

Just bear with me a little bit more .. things will fall in place.

Java plugin

Let us start with the most important of all for Java developers : the Java plugin. As expected, this plugin adds the following capabilities to a project:

compilation

testing

bundling

Logically, this is all we need to do with our Java projects, isn’t it ? The bundling typically would mean a JAR file in most cases.

Any plugin that you need to use should be added to the build.gradle file via the following statement:

In our case, since we want to use the Java plugin, we will use the following statement:

Let us get started with understanding what goes on behind the scenes.

Create a folder say example2 on your machine. You can use any folder that you wish on your machine. Just make sure that you follow one that suits your environment and that gradle is available from any directory on your machine.

Create our standard build.gradle in this folder. Just put the following line shown below in the build.gradle:

As discussed earlier, the plugin will add a bunch of tasks that know how to deal with typical Java projects. Let us fire the following command to understand what tasks have been added:

This will produce the following output ( I am only including the buildTasks from my output):

As you can see, various tasks have been added and most of them are intuitive enough for you as a Java Developer to understand. For e.g.

clean will delete a build directory, all your classes will be removed for a fresh compile.

assemble will create an archive (JAR) file from your Java project and will run all other tasks that require compile, test and so on.

These tasks could also depend on each other and so on. Refer to the documentation for a complete dependency graph of these tasks.

Basic Java Project with Gradle Java plugin

Given this basic information, the first question that should strike you is that now that you have added the Java plugin and have some idea about the tasks that it provides i.e. clean, assemble, build, etc. , how do we invoke it ?

We already know that to invoke the tasks, all we need to do is fire the gradle <task-name> command. That is fine but what about my Java project i.e. the .java files and so on. Where is that ? What are the rules to follow for that ? Is there any conventions to follow so that the plugin knows exactly what to do ? What if my folder structure is different, will Gradle be able to help there?

The above are valid questions and Gradle has an answer for all of them. In this tutorial, we shall be following convention which means that the Java plugin of Gradle will look for files in certain folders. But be assured that if you prefer a non-convential way, you can get those things addressed via Gradle too – just that it is out of scope of this tutorial series.

From the official documentation, the Java plugin expects the following folder structure for your Java code and Java test classes:



This is not mandatory and if you wish you could have different folders. Just look up SourceSets in the official documentation for the Java plugin to see how you can specify alternate Sourceset.

What is a SourceSet ? It identies the source i.e. grouping for your source files that need to be compiled and built together, some sort of a logical grouping or component. As per the official documentation, the Java plugin defines two standard source sets, called main and test. The main source set contains your production source code, which is compiled and assembled into a JAR file. The test source set contains your unit test source code, which is compiled and executed using JUnit or TestNG.

Given this information, we are going to put all our sources and test files as per the folder structure recommended by default by the Java plugin. This means:

Put all your Java files (full package,etc) into the src/main/java folder

Put all your Test files (JUnit stuff) into the src/main/test folder

I have gone ahead and put one Java source file inside of src/main/java folder. The folder structure as per the package is :



Inside this quoteapp folder i.e. package com.mindstorm.quoteapp I have my one single Java file named Quote.java as shown below:

Now, let us go back to the root folder i.e. example2 where I have my build.gradle file. The file still has just a single statement i.e. apply plugin: “java”

Now fire the following command as given below (Note that I have removed the -q so that you can see all the console output)

This will do all the hard work of compiling, building your Java files into a JAR file. Multiple tasks are fired, which shows that the assemble task in return depends on several tasks that it is executing first. The output of the command is shown below:

You can experiment with introducing compilation errors and seeing what happens if you try to run the assemble command.

What did the command result in? If everything went well, the command resulted in a build folder in your project root directory. Inside of that, you will find a libs folder and inside which is the example2.jar . Your file name might be different depending on your folder name.

If you wish to clean (empty) the build directory and do a clean build again, you can invoke a gradle clean command first and then a gradle assemble command.

Let us improve things a bit. What if we wanted to give a version to the JAR file and a different name. To do that, simply do the following:

Update your build.gradle so that it looks like this:

Now, fire the gradle assemble command and you should have a JAR file that is named as <name>-<version>.jar in the build/libs folder. In my case, it will be quote-1.0-FINAL.jar.

Note : You might be wondering from where do I produce stuff like archivesBaseName, etc. The only answer I can give is that the documentation is vast and you will have to navigate yourself through it. For e.g. go to the official page of the Java plugin, search for archivesBaseName and you will understand what is going on. My only advice is to keep the documentation handy, some property or the other will be there, just think logically and you will find the answer in the documentation.

The beginning of Dependencies

We have to tackle this sooner or later. And the moment has arrived. There is not much chance for any of your Java projects to not depend on 3rd party libraries. Consider the above Quote.java where we just wrote a simple Java Bean and there was nothing else to it. What if we modify it a bit by adding a toString() method implementation that makes use of the 3rd party library : Apache Commons JAR.

Let us first look at the code:

This is fairly straightforward Java stuff. Nothing much of interest here.

Obviously if you try to fire a gradle assemble command now, you will face issues. I suggest that you do, so that you can see the error reporting in all its glory. You should see the standard compilation error along with the BUILD FAILED message.

The solution is obviously to tweak the build.gradle file so that it is aware of 2 things:

What JAR files are needed to compile the code ?

Where to find those JAR files ?

Enter the world of Repositories in Gradle. A Repository in Gradle is a location where Gradle can locate the JAR files in our case. Gradle supports popular public repositories like Maven Central, Ivy and even your local repositories if you want. So if you are coming in from a Maven world, this will sound familiar.

To configure a Remote Repository, you will need to add the following element to your build.gradle file:

This will make Gradle look for any dependency (JAR files) that you specify in the Maven Central Repository.

Just keep this point in mind and we shall come to it later. Each file when published to Maven Central or any repository for that matter will have the following characteristics:

A Group Id (group)

An Artifact Id (name)

A Version (version)

So for example, if you want to locate the Apache Commons 3.3.2 JAR (which is the latest at this time of writing), it will be available in Maven Central as given below:



The above screen is taken from Maven Central repository. If you are in doubt for any of your dependent JAR files, simply search in the Maven Central Repository.

Allright, more on this later. But first, let us understand Configurations in Gradle.

Configurations provided by Java plugin

All we have talked about so far, is that we found that we need to provide the build.gradle file with additional information on which dependencies (JAR files) are needed to compile the code and where it can find that. We solved the where part by mentioning the mavenCentral() repository from where it can pick up the files. But we are yet to specify which files to build.gradle, especially in our case the Apache Commons Lang library.

Now, it is perfectly possible that you may one set of JAR files for compiling and maybe some additional files for testing and so on. For e.g. if you are compiling JUnit Test Cases and want to run them too, you might need to provide the JUnit JAR file under those circumstances. But under other situations for compiling your main source files, you may not need the JUnit JAR files.

To address this, Gradle has the concept of Confgiurations. A Configuration is simply a named set of dependencies. You can use them to declare the external dependencies of your project.

But what are these configurations and what are their names. It turns out that typically plugins will add a list of configurations that we can use to specify our dependencies. For the Java plugin, it adds several standard configurations that we can use to specify our dependencies. Some of these are:

compile

runtime

testCompile

testRuntime

I will quote the official documentation here:

compile

The dependencies required to compile the production source of the project.

runtime

The dependencies required by the production classes at runtime. By default, also includes the compile time dependencies.

testCompile

The dependencies required to compile the test source of the project. By default, also includes the compiled production classes and the compile time dependencies.

testRuntime

The dependencies required to run the tests. By default, also includes the compile, runtime and test compile dependencies.

Now, since we have to compile our Quote class that has a dependency on the Apache Commons Lang 3.3.2 , we add the following entry now to our build.gradle

Some notes on the dependencies block:

Each dependency is defined separately.

You specify first the configuration name i.e. compile in our case. Then you mention the library via its group, name and version as earlier mentioned.

Currently there is only one compile time dependency shown. But we can add more dependencies too. Just add them on each line.

Save this build.gradle and try running the gradle assemble command. This time, you will find that it will work by first downloading the JAR file from the Maven Central Repository, adding it to the local cache and then continuing with the compilation of the project.

Note (Very Important):
Make sure you are patient and you have a good working Internet connection, since Gradle looks in its local cache if you have already downloaded this library and version. If not, it will need to go to the Internet , access the Maven Central Repository, download it first and then continue with the build process. This process of doing a local check first and then downloading the artifacts is a major source of problem for most newcomers (I suffered from it!). The point is that if your dependencies are large for e.g. as we shall see in the later episodes (App Engine SDK), then it is a good 200 MB of download and you might just feel that the whole process has stopped working and has gone into a loop, whereas on the other hand, it is sincerely doing its job of downloading the JARs and any other transitive dependencies for you.

Hope things are becoming a bit clear and you have been able to successfully build your Java project via Gradle and which has dependencies on external libraries.

Some more dependencies

Now that we have seen how to add a compile dependency, it makes sense to look at what we should do to add a test dependency. Remember that we can add our own JUnit Test cases. These test cases (Java classes) would be present in the src/test/java folder.

Recollect that the Java plugin added the following configurations to the Gradle project:

compile

runtime

testCompile

testRuntime

Now, we can easily see that we will need to add the JUnit JAR file dependency to the testCompile configuration. To do that, simply add your JUnit Test classes to the respective source directories and update your build.gradle so that it looks like this:

The format should look familiar. Just that you will notice one difference in the version. There is a + sign instead of a specific version number. This is a hint to Gradle that it should go and fetch the latest version that is available.

Summing up

Hope you are getting the hang of Gradle now. We are barely scratching the surface in terms of its capabilities but you get the picture. I strongly recommend to go through the official documentation i.e. Gradle User Guide and more specifically the Java Quickstart  and Gradle Java plugin. I found repeated readings of this documentation very useful in my learning journey and I am still learning :-) I strongly recommend trying out this on your machine, since that will help you understand it more and provide you with ready templates that you can create for a variety of Java projects.

See you in the next episode, where we shall cover Building Multiple Java Projects with Dependencies, where we have one or more Java projects that are dependent on each other. For e.g. you write a library and the other Java project requires that. And moving forward, we will cover the Gradle War (Web Application Archive) plugin that helps with Java Web projects. Till then, Happy Gradling!

Show more