How Gradle Works (1) - Startup

This is the first blog of a series How Gradle Works, which includes the following topics:

  • How Gradle starts up
  • How many JVMs are involved in a Gradle build
  • What happens in each JVM during the build

We’ll explain the first topic How Gradle Starts Up in this blog. Before reading on, we assume you are familiar with basic JVM/Gradle concepts (jar, classpath, wrapper, daemon, project, task, etc.).

How Gradle Starts Up

There are many ways to start a Gradle build:

  • Local Gradle Distribution in CLI: /path/to/local/distribution/bin/gradle <SomeTask>
  • Gradle Wrapper in CLI: ./gradlew <SomeTask>
  • Click a button in your IDE to import a Gradle project or run some tests/tasks

What’s the difference? What happens under the hood? Before we start, we need to remember that Gradle is software running on top of a JVM (Java Virtual Machine).

Local Gradle Distribution in CLI

You may have downloaded a Gradle distribution to your local computer, either manually or by some package management tools. Suppose you start a Gradle build by typing /path/to/local/distribution/bin/gradle <SomeTask>. What happens then?

Open the /path/to/local/distribution/bin/gradle in your favorite text editor, you’ll find that it’s nothing magic but a plain text file, or more precisely, a shell script file. When you say /path/to/local/distribution/bin/gradle <SomeTask>, a shell process is started and starts executing the code in the script. At the end of this script, you can see a line:

exec "$JAVACMD" "$@"

So the script’s job is simple: it finds java command, determines the parameters, and starts a JVM by invoking exec. exec executes a command in the same process, i.e. replaces the current process with a JVM process.

The entry point of Gradle Client JVM is org.gradle.launcher.GradleMain class.

The JVM started by exec is a very lightweight JVM. Let’s call it Gradle Client JVM because it doesn’t run any real build logic. It searches for a compatible Gradle Daemon and connects to it via a local socket if found. If there is no such daemon running, it will start a daemon, which is also a JVM (Gradle Daemon JVM). Later,

  1. The Gradle Client JVM forwards input (command line arguments, environment variables, stdin, etc.) to the Gradle Daemon JVM.
  2. The Gradle Daemon JVM runs the build, and sends output (stdout/stderr) back to the Gradle Client JVM.
  3. After the build finishes, the Gradle Client JVM will exit, but the Gradle Daemon JVM may stay running for some time.

However, there is one exception for this workflow. If you pass --no-daemon to the Gradle build, the client JVM may convert itself into a daemon JVM if it is compatible with build requirements. In this case, there is no daemon, no communication, no input/output forwarding at all - the build happens inside the single JVM:

But if --no-daemon is present and the client JVM is not compatible with build requirements, a new disposable JVM will still be started for the build and exit at the end of the build:

This is how local Gradle distribution starts up on UNIX OSes. On Windows, the mechanism is very similar - the only difference is that we invoke /path/to/distribution/bin/gradle.bat instead of /path/to/distribution/bin/gradle.

Gradle Wrapper in CLI

As you may know, Gradle Wrapper is the recommended way to execute Gradle builds. What happens when you type ./gradlew <SomeTask>? What’s the difference with running from local Gradle distribution?

If you open gradlew in a text editor, you’ll find it very similar to what we have explained in the last section: there is no magic, just a shell script. It starts a tiny JVM from gradle/wrapper/gradle-wrapper.jar in your project. This JVM will locate or download a specific version of Gradle distribution declared in gradle/wrapper/gradle-wrapper.properties, then it will start Gradle inside the same JVM via Java reflection. After that, this tiny JVM acts as the Gradle Client JVM in the way explained in the last section.

The entry point of Gradle Wrapper JVM is the org.gradle.wrapper.GradleWrapperMain class.

Gradle in IDE

In the previous sections, Gradle Client JVM is a dedicated JVM started via CLI. Actually, it doesn’t have to be a dedicated JVM. Gradle client can be a part of another JVM, i.e. a JVM that loads some Gradle jars dynamically and acts as a “Gradle client”: searches/connects to the daemon, starts a new daemon, and communicates with the daemon. This programmatic API is called Tooling API.

For example, when you click Gradle Sync button in IntelliJ IDEA, IDEA will start a special Gradle build to fetch necessary information (project structure, dependencies, tasks, etc.) of the project via the Tooling API. Still, all the build logic happens in a Gradle Daemon JVM, and the Tooling API just reads a build result and returns it to the caller.

What’s Next

In the next blog post of the series we’ll explain how many JVMs are involved in a Gradle build.