How to make your Gradle build faster when you see - Starting a new Gradle Daemon for this build (subsequent builds will be faster)?


Recently I was working on one of my Spring Boot Application with Gradle as my preferred build framework. I aimed to deploy Spring Boot Application to AWS EC2 instance but as soon as I started my Gradle build on my EC2 instance it kind of stuck and frozen with the following message -

Starting a new Gradle Daemon for this build (subsequent builds will be faster)

Looking at the message I assumed Maybe I am running it for the first time that is why it is taking time but after waiting for a while it just failed with the following message -

wrapper script does not seem to be touching the log file in /tmp/jenkins/workspace/myworkspace@tmp/(JENKINS-48300: if on a laggy filesystem, consider -Dorg.jenkinsci.plugins.durabletask.BourneShellScript.HEARTBEAT_CHECK_INTERVAL=300) script returned exit code -1

Now it makes me wonder How come the same Gradle build is working fine on my development laptop but failing to finish on my AWS EC2 instance.

Here is my project setup which includes Jenkins pipelines also -

  1. Spring Boot application
  2. AWS EC2 Instance - t2.micro
  3. Gradle as my preferred build framework
  4. Jenkins pipeline for continuous integration and deployment

After doing a ton of google search and going through the StackOverflow posts eventually I summed up the following approaches which can help you to make your Gradle build faster.

For me increasing the AWS EC2 instance memory did the trick and the Gradle build went successful but have a look at the different approaches maybe you have a different setup than me and increasing the memory might not work for you.

Approaches for speeding up Gradle build

  1. Increase the memory of your AWS EC2 Instance
  2. Upgrade to the latest version of Gradle
  3. Enable gradle daemon org.gradle.daemon=true
  4. Use parallel execution org.gradle.parallel=true
  5. Increase Gradle daemon heap size org.gradle.jvmargs
  6. Use Multi Project approach
  7. Say no to dependencies at configuration time
  8. Use Gradle --profile for analyzing the build time
  9. Caching

1. Increase the memory of your AWS EC2 Instance

For me, I had to increase the memory of my AWS EC2 instance to fix the Gradle build issue.

Initially, I was using t2.micro with 1 GiB of memory and 1 vCPU but later on, I changed the EC2 instance type to t2.xlarge with 16 GiB of memory and 4 vCPU.

After the upgrade, my Gradle build inside my Jenkins pipeline was lightning fast and finished quickly.


2. Upgrade to the latest version of Gradle

The second golden rule of thumb is to use the latest version of Gradle because because I have often seen fixes and patches from Gradle in their releases.

So if there is any kind of bug related to performance or build is breaking due to xyz reason then the Gradle team would know it before you know, it is always advisable to use the latest version of Gradle so that you do not discover the issue on your own.

Also as you might know Gradle uses JVM as its run time, so Gradle and JVM go hand in hand. So whenever you are planning to upgrade the Gradle version, it is also recommended to upgrade JVM version whenever possible to get the maximum out of it.

Backward compatibility

All the versions of Gradle and JVM are backward compatible so in case if you are worried about breaking the stuff after the upgrade then it’s already been taken care of by Gradle and JVM, so you are safe.


3. Enable gradle daemon “org.gradle.daemon=true”

Well, this feature is very beneficial if you are working in the development environment but also can be useful if you have CI(continuous integration) like Jenkins.

All you have to do is create gradle.properties at the root level of your project and add the following configuration to enable the daemon.

1org.gradle.daemon=true 

Jenkins(Continuous Environment) - In your setup if you have multiple daemons running then it can benefit by reducing the turnaround time.


4. Use parallel execution org.gradle.parallel=true

The one more feature of Gradle which I like the most is parallel execution. Inside your gradle.properties if you set the property org.gradle.parallel=true then it will let you run Gradle tasks in parallel.

But before you set this flag, here are the key points which you should take into consideration -

  1. If you have a very big monolith project with only a single build.gradle then you will not gain anything
  2. But if you have small modules(projects) with their own build.gradle then it will for sure improve your build time.
  3. If you have a multimodule setup and each module is dependent on other modules then those dependent modules will take some time to compile. But another module should benefit more from the parallel build.


5. Increase Gradle daemon heap size org.gradle.jvmargs

Increasing the heap size - Well in any case if you see the performance degradation in your Gradle build then I would highly suggest you increase the heap size(if you have a decent amount of memory to spare).

To increase the heap size you simply need to add the following configuration into your gradle.properties -

1org.gradle.jvmargs=-Xmx4096M 

(*Note - Set the memory size as per your need)

An increase in the memory size will help you to hold caches of your bigger build and thus improving the performance.


6. Use Multi Project approach

Using Multi Project approach is a big undertaking because if you are looking for a quick fix that can improve your Gradle build time then I would not suggest this approach.

Going to implement Multi Project approach would require you to think at the architecture level because you need to break down your existing project into small modules with their own individual build.gradle.

Here is one of my multimodule project where I have created the following modules -

  1. spring-boot-app
  2. spring-boot-application-runner
  3. spring-boot-command-line-runner
  4. spring-boot-dev-tools
  5. spring-boot-dev-tools-restart-trigger-file
  6. spring-boot-rest-controller

Here is my build.gradle -

 1buildscript {
 2    ext {
 3        springBootVersion = '2.1.2.RELEASE'
 4    }
 5    repositories {
 6        mavenCentral()
 7    }
 8    dependencies {
 9        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
10    }
11}
12
13allprojects {
14    group 'com.jhooq'
15    version '1.0-SNAPSHOT'
16}
17
18subprojects{
19    repositories {
20        mavenCentral()
21    }
22
23    apply plugin: 'java'
24    apply plugin: 'idea'
25    apply plugin: 'org.springframework.boot'
26    apply plugin: 'io.spring.dependency-management'
27
28    sourceCompatibility = 1.8
29    targetCompatibility = 1.8
30
31    dependencies {
32        compile ("org.springframework.boot:spring-boot-starter")
33        compile ("org.springframework.boot:spring-boot-starter-test")
34    }
35}
36
37project(':spring-boot-dev-tools'){
38
39    configurations {
40        developmentOnly
41        runtimeClasspath {
42            extendsFrom developmentOnly
43        }
44    }
45
46    dependencies {
47        compile ("org.springframework.boot:spring-boot-starter-web")
48        developmentOnly("org.springframework.boot:spring-boot-devtools")
49    }
50
51}
52project(':spring-boot-dev-tools-restart-trigger-file'){
53
54    configurations {
55        developmentOnly
56        runtimeClasspath {
57            extendsFrom developmentOnly
58        }
59    }
60
61    dependencies {
62        compile ("org.springframework.boot:spring-boot-starter-web")
63        developmentOnly("org.springframework.boot:spring-boot-devtools")
64    }
65
66}
67project(':spring-boot-rest-controller'){
68    
69    dependencies {
70        compile ("org.springframework.boot:spring-boot-starter-web")
71    }
72
73}
74

7. Say no to dependencies at configuration time

Any kind of dependencies resolution at configuration time is Big NO because it will seriously degrade your build performance by increasing the build time.

Consider the following example in which we are simply trying to print a message during the Gradle build.

1tasks.register('sampleTask', Copy) {
2    println ">> File path : ${configurations.compileClasspath.files}"
3    into(layout.buildDirectory.dir('output'))
4    from(configurations.compileClasspath)
5} 

Here if you look carefully we are trying to resolve the variable ${configurations.compileClasspath.files} during the configuration time. So considering you have a very big multi-module project setup this will need extra resources to resolve it and print the message onto the console.

If you have Gradle Enterprise then you can easily check Cumulative time spent resolving dependencies for Project configuration, Task execution.

You can find all these by going into Build Scan -> Performance -> Dependency Resolution


8. Use Gradle –profile for analyzing the build time

Well if you are really not in good luck and following all the good practices of the book but still you do not see really good improvement in the Gradle build performance. Then I would suggest you try the Gradle Profiler which can help you to point out the underlying issues with your Gradle build.

The simplest way to use the --profile is -

1gradle --profile <your-gradle-task-name> 

Once you run the Gradle with profile parameter, it will generate an html file

Here is the sample Gradle profile report snapshot showing -

  1. Total Build Time
  2. Startup
  3. Settings and BuildSrc
  4. Loading Projects
  5. Configuring Projects
  6. Task execution

Also, you can use Gradle profilers along with popular profiles tools such as - JProfiles and YourKit


9. Caching

Another feature of Gradle build is caching. With the help of caching, you can save the build output of the previously executed Gradle build, so that you do not have to run Gradle build for the same task again(provided there is no change in the code).

You can save the build cache locally so that it is readily available or else the best option would be to use Gradle Enterprise Build Cache.