Getting Started With Gradle: Dependency Management

It is challenging, if not impossible, to create real life applications which don't have any external dependencies. That is why dependency management is a vital part of every software project.

This blog post describes how we can manage the dependencies of our projects with Gradle. We will learn to configure the used repositories and the required dependencies. We will also apply this theory to practice by implementing a simple example application.

Let's get started.

Additional Reading:

If you are not familiar with Gradle, you should read the following blog posts before you continue reading this blog post:

Introduction to Repository Management

Repositories are essentially dependency containers, and each project can use zero or more repositories.

Gradle supports the following repository formats:

Let’s find out how we can configure each repository type in our build.

Adding Ivy Repositories to Our Build

We can add an Ivy repository to our build by using its url address or its location in the local file system.

If we want to add an Ivy repository by using its url address, we have to add the following code snippet to the build.gradle file:

repositories {
    ivy {
        url 'http://ivy.petrikainulainen.net/repo'
    }
}

If we want to add an Ivy repository by using its location in the file system, we have to add the following code snippet to the build.gradle file:

repositories {
    ivy {       
        url '../ivy-repo'
    }
}

Let’s move on and find out how we can add Maven repositories to our build.

Adding Maven Repositories to Our Build

We can add a Maven repository to our build by using its url address or its location in the local file system.

If we want to add a Maven repository by using its url, we have to add the following code snippet to the build.gradle file:

repositories {
    maven {
        url 'http://maven.petrikainulainen.net/repo'
    }
}

If we want to add a Maven repository by using its location in the file system, we have to add the following code snippet to the build.gradle file:

repositories {
    maven {       
        url '../maven-repo'
    }
}

Gradle has three "aliases" which we can use when we are adding Maven repositories to our build. These aliases are:

  • The mavenCentral() alias means that dependencies are fetched from the central Maven 2 repository.
  • The jcenter() alias means that dependencies are fetched from the Bintray's JCenter Maven repository.
  • The mavenLocal() alias means that dependencies are fetched from the local Maven repository.

If we want to add the central Maven 2 repository in our build, we must add the following snippet to our build.gradle file:

repositories {
    mavenCentral()
}

Let's move on and find out how we can add flat directory repositories to our build.

Adding Flat Directory Repositories to Our Build

If we want to use flat directory repositories, we have to add the following code snippet to our build.gradle file:

repositories {
    flatDir {
        dirs 'lib'
    }
}

This means that dependencies are searched from the lib directory. Also, if we want to, we can use multiple directories by adding the following snippet to the build.gradle file:

repositories {
    flatDir {
        dirs 'libA', 'libB'
    }
}

Let's move on and find out how we can manage the dependencies of our project with Gradle.

Introduction to Dependency Management

After we have configured the repositories of our project, we can declare its dependencies. If we want to declare a new dependency, we have to follow these steps:

  1. Specify the configuration of the dependency.
  2. Declare the required dependency.

Let's take a closer look at these steps.

Grouping Dependencies into Configurations

In Gradle dependencies are grouped into a named set of dependencies. These groups are called configurations, and we use them to declare the external dependencies of our project.

The Java plugin specifies several dependency configurations which are described in the following:

  • The dependencies added to the compile configuration are required when our the source code of our project is compiled.
  • The runtime configuration contains the dependencies which are required at runtime. This configuration contains the dependencies added to the compile configuration.
  • The testCompile configuration contains the dependencies which are required to compile the tests of our project. This configuration contains the compiled classes of our project and the dependencies added to the compile configuration.
  • The testRuntime configuration contains the dependencies which are required when our tests are run. This configurations contains the dependencies added to compile, runtime, and testCompile configurations.
  • The archives configuration contains the artifacts (e.g. Jar files) produced by our project.
  • The default configuration group contains the dependencies which are required at runtime.

Let's move on and find out how we can declare the dependencies of our Gradle project.

Declaring the Dependencies of a Project

The most common dependencies are called external dependencies which are found from an external repository. An external dependency is identified by using the following attributes:

  • The group attribute identifies the group of the dependency (Maven users know this attribute as groupId).
  • The name attribute identifies the name of the dependency (Maven users know this attribute as artifactId).
  • The version attribute specifies the version of the external dependency (Maven users know this attribute as version).
These attributes are required when you use Maven repositories. If you use other repositories, some attributes might be optional.

For example, if you use a flat directory repository, you might have to specify only name and version.

Let’s assume that we have to declare the following dependency:

  • The group of the dependency is 'foo'.
  • The name of the dependency is 'foo'.
  • The version of the dependency is 0.1.
  • The dependency is required when our project is compiled.

We can declare this dependency by adding the following code snipped to the build.gradle file:

dependencies {
	compile group: 'foo', name: 'foo', version: '0.1'
}

We can also declare the dependencies of our project by using a shortcut form which follows this syntax: [group]:[name]:[version]. If we want to use the shortcut form, we have to add the following code snippet to the build.gradle file:

dependencies {
	compile	'foo:foo:0.1'
}

We can also add multiple dependencies to the same configuration. If we want to use the "normal" syntax when we declare our dependencies, we have to add the following code snippet to the build.gradle file:

dependencies {
	compile (
		[group: 'foo', name: 'foo', version: '0.1'],
		[group: 'bar', name: 'bar', version: '0.1']
	)
}

On the other hand, if we want to use the shortcut form, the relevant part of the build.gradle file looks as follows:

dependencies {
	compile 'foo:foo:0.1', 'bar:bar:0.1'
}

It is naturally possible to declare dependencies which belong to different configurations. For example, if we want to declare dependencies which belong to the compile and testCompile configurations, we have to add the following code snippet to the build.gradle file:

dependencies {
	compile group: 'foo', name: 'foo', version: '0.1'
	testCompile group: 'test', name: 'test', version: '0.1'
}

Again, it is possible to use the shortcut form. If we want to declare the same dependencies by using the shortcut form, the relevant part of the build.gradle file looks as follows:

dependencies {
	compile 'foo:foo:0.1'
	testCompile 'test:test:0.1'
}

We have now learned the basics of dependency management. Let’s move on and implement our example application.

Creating the Example Application

The requirements of our example application are described in thefollowing:

  • The build script of the example application must use the Maven central repository.
  • The example application must write the received message to log by using Log4j.
  • The example application must contain unit tests which ensure that the correct message is returned. These unit tests must be written by using JUnit.
  • Our build script must create an executable jar file.

Let’s find out how we can fulfil these requirements.

Configuring the Repositories of Our Build

One of the requirements of our example application was that its build script must use the Maven central repository. After we have configured our build script to use the Maven central repository, its source code looks as follows (The relevant part is highlighted):

apply plugin: 'java'

repositories {
    mavenCentral()
}

jar {
    manifest {
        attributes 'Main-Class': 'net.petrikainulainen.gradle.HelloWorld'
    }
}

Let’s move on and declare the dependencies of our example application.

Declaring the Dependencies of Our Example Application

We have to declare two dependencies in the build.gradle file:

  1. Log4j (version 1.2.17) is used to write the received message to the log.
  2. JUnit (version 4.11) is used to write unit tests for our example application.

After we have declared these dependencies, the build.gradle file looks as follows (the relevant part is highlighted):

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    compile 'log4j:log4j:1.2.17'
    testCompile 'junit:junit:4.11'
}

jar {
    manifest {
        attributes 'Main-Class': 'net.petrikainulainen.gradle.HelloWorld'
    }
}

Let’s move on and write some code.

Writing the Code

In order to fulfil the requirements of our example application, "we have to over-engineer it". We can create the example application by following these steps:

  1. Create a MessageService class which returns the string 'Hello World!' when its getMessage() method is called.
  2. Create a MessageServiceTest class which ensures that the getMessage() method of the MessageService class returns the string 'Hello World!'.
  3. Create the main class of our application which obtains the message from a MessageService object and writes the message to log by using Log4j.
  4. Configure Log4j.

Let’s go through these steps one by one.

First, we have to create a MessageService class to the src/main/java/net/petrikainulainen/gradle directory and implement it. After we have do this, its source code looks as follows:

package net.petrikainulainen.gradle;

public class MessageService {

    public String getMessage() {
        return "Hello World!";
    }
}

Second, we have create a MessageServiceTest to the src/test/java/net/petrikainulainen/gradle directory and write a unit test to the getMessage() method of the MessageService class. The source code of the MessageServiceTest class looks as follows:

package net.petrikainulainen.gradle;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class MessageServiceTest {

    private MessageService messageService;

    @Before
    public void setUp() {
        messageService = new MessageService();
    }

    @Test
    public void getMessage_ShouldReturnMessage() {
        assertEquals("Hello World!", messageService.getMessage());
    }
}

Third, we have create a HelloWorld class to the src/main/java/net/petrikainulainen/gradle directory. This class is the main class of our application. It obtains the message from a MessageService object and writes it to a log by using Log4j. The source code of the HelloWorld class looks as follows:

package net.petrikainulainen.gradle;

import org.apache.log4j.Logger;

public class HelloWorld {

    private static final Logger LOGGER = Logger.getLogger(HelloWorld.class);

    public static void main(String[] args) {
        MessageService messageService = new MessageService();

        String message = messageService.getMessage();
        LOGGER.info("Received message: " + message);
    }
}

Fourth, we have to configure Log4j by using the log4j.properties which is found from the src/main/resources directory. The log4j.properties file looks as follows:

log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n

log4j.rootLogger=DEBUG,Stdout

That is it. Let's find out how we can run the tests of our example application.

Running the Unit Tests

We can run our unit test by using the following command:

gradle test

When our test passes, we see the following output:

> gradle test
:compileJava
:processResources
:classes
:compileTestJava
:processTestResources 
:testClasses
:test

BUILD SUCCESSFUL

Total time: 4.678 secs

However, if our unit test would fail, we would see the following output (the interesting section is highlighted):

> gradle test
:compileJava
:processResources
:classes
:compileTestJava
:processTestResources
:testClasses
:test

net.petrikainulainen.gradle.MessageServiceTest > getMessage_ShouldReturnMessageFAILED
    org.junit.ComparisonFailure at MessageServiceTest.java:22

1 test completed, 1 failed
:test FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///Users/loke/Projects/Java/Blog/gradle-examples/dependency-management/build/reports/tests/index.html

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 4.461 secs

As we can see, if our unit tests fails, the describes:

  • which tests failed.
  • how many tests were run and how many tests failed.
  • the location of the test report which provides additional information about the failed (and passed) tests.

When we run our unit tests, Gradle creates test reports to the following directories:

  • The build/test-results directory contains the raw data of each test run.
  • The build/reports/tests directory contains a HTML report which describes the results of our tests.

The HTML test report is very useful tool because it describes the reason why our test failed. For example, if our unit test would expect that the getMessage() method of the MessageService class returns the string 'Hello Worl1d!', the HTML test report of that test case would look as follows:

testfailure

Let’s move on and find out how we can package and run our example application.

Packaging and Running Our Example Application

We can package our application by using one of these commands: em>gradle assembly or gradle build. Both of these commands create the dependency-management.jar file to the build/libs directory.

When run our example application by using the command java -jar dependency-management.jar, we see the following output:

> java -jar dependency-management.jar
 
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/Logger
	at net.petrikainulainen.gradle.HelloWorld.<init>(HelloWorld.java:10)
Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Logger
	at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 1 more

The reason for this exception is that the Log4j dependency isn’t found from the classpath when we run our application.

The easiest way to solve this problem is to create a so called "fat" jar file. This means that we will package the required dependencies to the created jar file.

After we have followed the instructions given in this blog post, our build script looks as follows (the relevant part is highlighted):

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    compile 'log4j:log4j:1.2.17'
    testCompile 'junit:junit:4.11'
}

jar {
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    manifest {
        attributes 'Main-Class': 'net.petrikainulainen.gradle.HelloWorld'
    }
}

We can now run the example application (after we have packaged it) and as we can see, everything is working properly:

> java -jar dependency-management.jar 
INFO  - HelloWorld                 - Received message: Hello World!

That is all for today. Let’s summarize what we learned from this blog post.

Summary

This blog post has taught us four things:

  • We learned how we can configure the repositories used by our build.
  • We learned how we can declare the required dependencies and group these dependencies into configurations.
  • We learned that Gradle creates a HTML test report when our tests are run.
  • We learned how we can create a so called "fat" jar file.

If you want to play around with the example application of this blog post, you can get it from Github.

If you want to learn how to use Gradle, you should take a look at my Gradle tutorial.
19 comments… add one
  • Niranjan Deshpande Sep 6, 2014 @ 12:57

    Nice one again. :) .. But i tried clicking on the cook book page, it's deleted. Was looking for more information on the "fat" jar file and configurations.compile.collect

    • Petri Sep 6, 2014 @ 13:57

      Thanks for pointing that out. I replaced the dead link with a link that points to a blog post which provides additional information about the "fat jar" approach.

  • Prashanth Dec 20, 2014 @ 15:37

    Great post.. you saved my day Thanks Petri

    • Petri Dec 20, 2014 @ 15:45

      You are welcome! I am happy to hear that this blog post was useful to you.

  • Eswar Aug 16, 2015 @ 11:29

    This post useful to know about gradle dependency management

    • Petri Aug 16, 2015 @ 21:49

      Thank you for your kind words. I really appreciate them.

  • Sivaraj Sep 15, 2015 @ 14:30

    My build was successful. But I could not find "build/test-results directory "
    Here I listed the files and directories present in my gradle project.

    [prj1]$ ls -R

    ./src/main/test/net:
    petrikainulainen

    ./src/main/test/net/petrikainulainen:
    gradle

    ./src/main/test/net/petrikainulainen/gradle:
    MessageServiceTest.java

    ./src/test:
    java resources

    ./src/test/java:

    ./src/test/resources:

    Update: I removed the unnecessary files since they made this comment unreadable - Petri

    • Petri Sep 15, 2015 @ 19:26

      The problem is the MessageServiceTest class in the /src/main/test/net/petrikainulainen/gradle directory. It should be in the src/test/java/net/petrikainulainen/gradle directory.

      • Weeber Apr 11, 2016 @ 17:43

        I hit the same issue and fixed it thanks to your comment but the article has to be corrected since it instructs to create the test in the src/test/net/petrikainulainen/gradle directory instead of the src/test/java/net/petrikainulainen/gradle one where the plugin expects it to be.

        Quote from article: "Second, we have create a MessageServiceTest to the *src/test/net/petrikainulainen/gradle* directory and write a unit test to the getMessage() method of the MessageService class. The source code of the MessageServiceTest class looks as follows"

        • Petri Apr 11, 2016 @ 19:32

          Hi Weeber,

          I thought that I fixed that error ages ago :( Anyway, thank you for reporting this error. This time I fixed it before I wrote this comment.

  • Andrew Oct 22, 2015 @ 11:57

    Thank you for the helpful posts. I was having problems with log4j, but then I updated the dependencies statement (https://logging.apache.org/log4j/2.x/maven-artifacts.html):

    " compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.4.1'
    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.4.1'
    "
    I am having trouble with the junit dependency. I get "package .. does not exist" errors for everything involving junit. For example:
    "
    error: package org.junit does not exist import org.junit.Before;
    "

    • Andrew Oct 22, 2015 @ 12:21

      Woops. I saved MessageServiceTest to the src/main/java... directory instead of src/main/test...

      Now everything is working.

      Thank you again for the tutorials.

      • Petri Oct 23, 2015 @ 21:49

        Hi Andrew,

        It's good to hear that you were able to solve your problem. Also, thank you for your kind words. I really appreciate them.

  • Brendan Jan 27, 2016 @ 1:42

    There's a typo at:
    "Second, we have create a MessageServiceTest to the src/main/test/net/petrikainulainen/gradle"

    The directory should be "src/test/java/net/petrikainulainen/gradle"

    • Petri Jan 27, 2016 @ 9:19

      Oops. I fixed it. Thank you for reporting it.

  • MikeR Sep 20, 2021 @ 18:26

    In each of the code snippets quotes are being rendered thus:
    return "Hello World!";
    would be much nicer as:
    return "Hello World!";
    But let me say, awesome guide! I really enjoy reading it.

    • MikeR Sep 20, 2021 @ 18:28

      Haha, it rendered my code snippet quotes correctly. The code snippets above have embedded \& quot \;

      • Petri Sep 20, 2021 @ 22:45

        Good catch. This problem happens periodically and unfortunately I have no idea what causes it. Anyway, it should be fixed now. Thank you for reporting this issue because now I know that I have to fix a handful of other posts too.

        Edit: It seems that this behavior might be caused by this WP feature.

Leave a Reply