Gradle Tutorial : Part 2 : Java Projects

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.

LAST UPDATE : January 21, 2015 :
i) Corrected build.gradle file
ii) Final Project available for download now

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:

apply plugin: <plugin-name>

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

apply plugin: "java"

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:

apply plugin: "java"

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:

gradle tasks

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

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend
on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles classes 'main'.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles classes 'test'.

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

img4

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 identifies 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 i.e. I have created a package named “com.mindstorm.quoteapp” inside the src/main/java folder and in that package folder structure, I have put the Java file (Quote.java).

The folder structure as per the package is :

img5

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

package com.mindstorm.quoteapp;

public class Quote {
 private Long id;
 private String who;
 private String what;
 
 public void setId(Long id) {
 this.id = id;
 }
 public void setWho(String who) {
 this.who = who;
 }
 public void setWhat(String what) {
 this.what = what;
 }
 public Long getId() {
 return id;
 }
 public String getWho() {
 return who;
 }
 public String getWhat() {
 return what;
 }
}

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)

gradle assemble

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:

E:\gradle-projects\example2>gradle assemble
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:assemble

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.

You can also fire the following task which does all the check and assemble for you:

gradle -q build

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:

apply plugin: 'java'
archivesBaseName = "quote"
version = '1.0-FINAL'

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:

package com.mindstorm.quoteapp;
import org.apache.commons.lang3.builder.ToStringBuilder;

public class Quote {
 private Long id;
 private String who;
 private String what;
 
 public void setId(Long id) {
 this.id = id;
 }
 public void setWho(String who) {
 this.who = who;
 }
 public void setWhat(String what) {
 this.what = what;
 }
 public Long getId() {
 return id;
 }
 public String getWho() {
 return who;
 }
 public String getWhat() {
 return what;
 }

 public String toString() {
 return ToStringBuilder.reflectionToString(this);
 }
}

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:

repositories {
  mavenCentral()
}

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:

img1

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

apply plugin: 'java'
archivesBaseName = "quote"
version = '1.0-FINAL'

repositories {
 mavenCentral()
}

dependencies {
 compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.3.2'
}

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.
  • There is also a short form of specifying the dependency library. Instead of using group:value,name:value,version:value, you can just give the value in the form ‘group-value:name-value:version-value’. As an example, we could have rewritten the above dependencies as given below:
dependencies {
  compile 'org.apache.commons:commons-lang3:3.3.2'
}

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:

apply plugin: 'java'
archivesBaseName = "quote"
version = '1.0-FINAL'

repositories {
 mavenCentral()
}

dependencies {
 compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.3.2'
 testCompile group: 'junit', name: 'junit', version: '4.+'
}

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.

Download

You can download the entire project over here.

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 Part 3 : 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!

33 thoughts on “Gradle Tutorial : Part 2 : Java Projects

  1. Thanks! Very nice explanation, written very easy to read. Much more understandable and clean then off doc for start. Would be nice to see some tips and tricks which bother Android and gradle.

  2. This tutorial is great. I’m in the process of developing my first Android app and this is covering a ton of questions I originally had. Also, I think you have a typo in your “What is a SourceSet ? It identies…” paragraph. Identies should probably be identifies.

  3. Hey.
    I got to the part where is supposed to fail the build since I haven’t declared maven. What should I do? I have everything in place, folders, gradle 2.3, Quote.java with imports, etc…

    It keeps creating a JAR with only the manifest.

    What could possibly be happening?

  4. Btw, is there a small typo when talking about the test sources? it says:
    Put all your Test files (JUnit stuff) into the src/main/test folder

    Should it be
    /src/test/java

  5. I have a small doubt. I have a sep. project called “libraries” (not a java project ) having all the jars . I have used the jars in this project in my build path for all other java projects. How should i go about it in build.gradle

  6. How to Solve this……
    when i write “gradle assemble”
    It shows error “package org.apache.commons.lang3.builder” does not exist
    import org.apache.commons.lang3.builder.ToStringBuilder;

      1. Can you elaborate?
        I am following the tutorial to the letter, and everything is great until I get to adding the “repositories { mavenCentral() } ” to the script.
        I get the same error.
        What is “Commons Package” ? Where is “compile dependency”?
        Up to the part where I add “ToStringBuilder” there is nothing about “compile dependency”

        I’ve been writing C/C++ for 25 years, and trying to learn this. I can’t see how “mavenCentral” (whatever that is) can download a dependency “on the fly” if there is no setting anywhere to tell it where to download if from.

        In fact, in that section “Beginning of Dependencies” you state it needs to know:
        “What JAR files are needed to compile the code ?”
        “Where to find those JAR files ?”
        (Very clear and obvious issues in building software applications)
        However, simply saying to add “mavenCentral()” into the build.gradle script doesn’t answer either of those questions.

        And am currently stalled at this point, of an otherwise very well assembled tutorial.

        Can you provide more info, and get me past this same error?

      2. Thank you for the feedback/comments. Gradle build needs to have the dependencies (libraries) available that your application depends on. In your build.gradle files, you usually only mention the library name and version. This is all you tell Gradle. Now it is upto Gradle to go and download these libraries. There are multiple repositories available publicly where the libraries are published. One of them is the Maven Central Repository and an example search for various libraries is here(http://search.maven.org/#search%7Cga%7C1%7Ccommons%20apache). So when we mention that the list of repositories that Gradle can search for is mavenCentral(), internally Gradle knows where to fetch it from. This requires a working internet connection to go ahead and download the libraries. Gradle supports Maven Central, Ivy Repository and also your own local repository paths.

        Hope this helps clarify what goes on behind the scenes.

  7. Hello,
    I followed steps mentioned above. assemble and build is going good from command line.

    I am using eclipse but still i can see error symbol on import and usage lines.

    Any suggestion how to add it to project classpath.

Leave a reply to Adam Cancel reply