Java projects are always big projects

The recently released release 6 of the modern build tool brings a lot of new features that allow Java, Groovy, Kotlin and Scala projects to be better structured and modularized. A highlight is the introduction of the Gradle Module Metadata format to publish and reuse modules with extensive metadata.

Over the last few years, Gradle has been expanded to include more and more features that allow projects to be better structured and individual parts to be better isolated. The next big step has now been taken with Gradle 6, which provides new functions for managing dependencies that can be used across project and module boundaries. A central concept here is that each software module provides several variants from which a selection is made depending on the context.

In this article we present the most interesting features using a sample project that is available for self-exploration on GitHub [1]. It is a Gradle multiproject in which several projects are defined together in one build. Each of these projects can declare dependencies on other projects and on published modules (in this article we use the term "project" for local projects that are part of the build, and the term "module" for published libraries).

To demonstrate the structure in several projects, we define three Java projects: : app, : services and : data (Fig. 1). We use Apache Commons Lang as an example of a module. The multiproject can also be viewed in a Gradle Build Scan [2] (box: "Gradle Build Scans").

Gradle Build Scans

If you start a gradle build with the parameter --scan, various data about the build are collected and made available in a build scan. The build scan can be viewed via a secret link on scans.gradle.com and allows the build to be analyzed, for example for performance problems.

You can test this with the sample project [1] or directly look at a build scan that was created while working on this article [2]. In the context of the article, the view of the project's dependencies is particularly interesting.

Separate API and implementation dependencies

If you are working on an existing project, at some point there will be many dependencies on the project's classpath. This is due to the fact that all transitive dependencies are required to create executable software. In the case of our example (Fig. 1) Are defined : services the dependency on Apache Commons Lang. This is then also transitive in the : appProject available even though the : app-Project does not need it directly (no classes from Apache Commons Lang are referenced there). In large projects, this leads to an unnecessarily confusing jungle of dependencies, which can lead to many problems over time. For example, transitive dependencies are often used accidentally and not declared directly. Shall we z. B. Apache Commons Lang in the implementation of : app then we should define the corresponding dependency there directly. Otherwise, removing the dependency from the :serviceProject lead to the : appProject breaks. Although Apache Commons Lang is only an implementation detail of : services that is used internally (not in public interfaces) and should not influence other projects.

It therefore makes sense to distinguish between the runtime class path (all dependencies) and the compile-time class path (dependencies required for compiling). By separating API and implementation dependencies, Gradle offers the possibility of precisely controlling this.

Listing 1

Listing 1 shows the build file of the : servicesProject (services / build.gradle.kts). The build is written in Gradle's Kotlin DSL (box: “Kotlin DSL or Groovy DSL”). The first important innovation is the use of the java-library-Plug-ins. It provides the functionality for API dependencies and can be combined with other plug-ins (groovy, scala, kotlin).

Kotlin DSL or Groovy DSL

In Gradle 5, the Kotlin DSL was introduced as an alternative to the well-known Groovy DSL as the syntax for Gradle build files. As the name suggests, the syntax is based on Kotlin and is therefore statically typed compared to Groovy DSL. The main advantage is that the IDE (IntelliJ or Eclipse) has more context information and supports the author more strongly when creating the build files (e.g. through prompt error reporting and code completion). If you do not have a language preference, it is therefore recommended to use the Kotlin DSL.

Otherwise, both DSLs offer the same features, and the model of the project that is built up by the build files is independent of the DSL used. Therefore, you can choose freely in the end and also fall back on Groovy, if you z. B. is more familiar with Groovy or other parts of the project are developed in Groovy. The combination of both languages ​​(one build file in Groovy DSL, another in Kotlin DSL) in the same project is also possible without any problems.

Which DSL is used can be recognized by the name of the build file: build.gradle (Groovy DSL) or build.gradle.kts (Kotlin DSL).

The example in this article uses the Kotlin DSL. However, the Groovy syntax is often similar or even identical. If in doubt, the relevant topic can be looked up in the Gradle user manual [3], which contains all examples in both languages.

If we do the dependencies-Block looking at Listing 1, we can see that different keywords - api and implementation - can be used to create dependencies on other projects - api (project (": data")) - or modules - implementation ("org.apache.commons: co ...