Igor's Techno Club

Adding Error Prone checker Java 8 Gradle project

Error Prone is a Java code checker developed by Google. In comparison to other code checkers like PMD and FindBugs, it excels at identifying more concise, hard-to-notice bugs. It is actively developed and has a wide list of rules.

Initially, I thought that adding this checker to an existing legacy project would be a trivial task. However, surprisingly, just adding a Gradle plugin didn't work. Therefore, I integrated it directly into the Gradle build file. In this article, I'll show you how to add Error Prone to your Java 8 project. For a quick summary, refer to the TLDR section.

Project Structure

Our project is a monolith code base with more than 100 sub-projects and almost 500K lines of code, all written in Java 8. The code is built using Java 8 JVM and Gradle 7.4.1.

Gradle Plugin

The recommended way of using Error Prone plugin with Gradle project didn't work for me. So I had to integrate it directly into our Gradle build file. Thankfully, just by reading the plugin code and referring to other examples on GitHub made task task much easier for me.

The Goal

My goal was to integrate Error Prone at the project level that my team was responsible for, instead of adding it to the root build file. This approach had a few advantages:

Enabling Error-Prone in Gradle

For every module, Gradle creates a task called compileJava, which is used for compiling source code. Error Prone provides a Java compiler that can be used as a replacement for the default Javac to execute the compileJava task. To achieve this, we use the fork option of Gradle, which initiates a new child process for the Java compilation task:

compileJava {
    options.fork = true
    ...
}

If you're using any annotation processors like Lombok, you'll need to include them in the options as well:

options.annotationProcessorPath += configurations.checkerFrameworkCheckerJar

To use the Error Prone compiler, you'll need to set it as the bootclasspath option in compilerArgs:

options.compilerArgs = ["-Xbootclasspath/p:${configurations.errorProneJavac.asPath}".toString(),
                       '-XDcompilePolicy=simple',
                       '-Xplugin:ErrorProne']

and jvmArgs :

options.forkOptions.with {
    jvmArgs = ["-Xbootclasspath/p:${configurations.errorProneJavac.asPath}".toString()]
}

One gotcha to be aware of is the version of the Error Prone jar to use. In my case, due to using Java 8, I had to stick to version 2.10.

Here are the most common Error-Prone bugs I found:

TLDR Version

To quickly integrate Error Prone into the Java compile task, add the following snippet: Add the following snipped to Java compile task:

afterEvaluate {
    compileJava {
        options.fork = true
        options.annotationProcessorPath += configurations.checkerFrameworkCheckerJar
        options.compilerArgs = ["-Xbootclasspath/p:${configurations.errorProneJavac.asPath}".toString(),
                               '-XDcompilePolicy=simple',
                               '-Xplugin:ErrorProne']
        options.forkOptions.with {
            jvmArgs = ["-Xbootclasspath/p:${configurations.errorProneJavac.asPath}".toString()]
        }
    }
}

Use these specific versions:

errorProneJavac group: 'com.google.errorprone', name: 'javac', version: '9+181-r4173-1'

checkerFrameworkCheckerJar(
        'com.google.errorprone:error_prone_core:2.10.0',
        'org.checkerframework:dataflow-errorprone:3.15.0'
)

Happy coding!

Discuss on Twitter

#codequality #gradle #java