Java compile-time static analysis with Error Prone and NullAway

❝A guide into Google's Error Prone compile-time static analysis plug-in and Uber's NullAway❞
Contents

Static analysis is often used to identify problematic or dubious pieces of code early. This way we can avoid bugs and mistakes. In the Java ecosystem many already know the standard Java compiler warnings and errors, the feedback from your IDE, Pmd, Checkstyle, SpotBugs (or its predecessor FindBugs), and the likes. However, there are more solutions available, some of them lesser known .. at least to me.

With the introduction of Java’s compiler plug-in system, it has become possible to introduce additional checks at compile-time. This means that during a build you will get feedback on all typical warnings and errors, but in addition these plug-ins will also be executed.

Error Prone

Google’s Error Prone is a compiler plug-in that performs additional validation at compile-time. Error Prone has an impressive list of error patterns to look for. This is not unlike static analysis tools like Pmd, but it is very effective given that it is run at compile-time as part of Java’s compilation process.

Error Prone offers a list of bug patterns that is enabled by-defaut and a list of bug patterns that are of an “experimental” nature. These are disabled by default. There are a number of plug-in arguments to regulate the behavior of Error Prone. You can find the arguments in the source code for reference.

The most interesting arguments:

With Error Prone and these options available, we can extend the Java compiler with a lot more checking capability. This allows us to identify more bugs and dubious code constructs early.

In addition to performing these bug checks, Error Prone has the capability to do some automated code-base patching and refactoring. I have not looked into these features.

Error Prone plug-in for IntelliJ IDEA

There exists an Error Prone plug-in for IntelliJ IDEA. Installing this plug-in by JetBrains will enable access to the “Javac with error-prone” compiler such that you will get the Error Prone compiler results intergrated in your IDE.

There are two steps to installing this plug-in:

  1. Install the Error Prone plug-in.
  2. Configure the Java compiler: Javac with error-prone

After that, Error Prone is active for every build you execute. Further information can be found in the plug-in description and documentation.

JSR-305

JSR-305 is a Java suggestion for annotations that allow for the analysis of various Java language characteristics that go beyond the local scope, such as the handling of nulls. Officially, the JSR-305 improvement is Dormant. However, many IDEs and static analysis tools and plug-ins still recognize and respect the annotations.

Please note that JSR-305 annotations such as @Nonnull, @Nullable and @ParametersAreNonnullByDefault are hints only. The annotations themselves exert no influence and the Java compiler does not pay special attention to them. Static analysis tools, though, use these hints to lift their analysis capability out of the local scope - the implementation of the method only - to global scope - the use of the method in each location. As will Uber’s NullAway plug-in for Error Prone.

NullAway

Apart from the bug patterns that Error Prone provides, Error Prone itself is a framework of sorts. Uber’s NullAway plug-in hooks into the static analysis mechanisms of Error Prone. It will effectively provide additional “Bug Patterns” for analysis at compile-time. NullAway watches for @Nullable annotations from various packages, such as: JSR-305, and IntelliJ’s and FindBugs’ and SpotBugs’ annotations.

However, as mentioned, NullAway operates at compile-time. It can error out on nullability errors. This effectively is very close to designing a language that does not accept null values, at all or by default.

Whenever NullAway is active, it is assumed that variables, fields, method parameters and return types are intended to be non-null, unless otherwise specified. Otherwise specified meaning that the entity is annotated with @Nullable. Although the @Nonnull annotation exists, it doesn’t carry any special meaning, given that it is the default mode for NullAway.

Configuring NullAway-like behavior in your IDE

Before we dive into the Maven configuration, there is a way to approach NullAway’s analysis results using the IDE’s static analysis capabilities. That is, let IDE assume non-null parameters by default such that it assists you with in-editor hints.

For each package where we assume non-null behavior, the package can be annotated with @ParametersAreNonnullByDefault. IDE’s, such as IntelliJ IDEA, with sufficient understanding of JSR-305 will respect the annotation and warn you whenever a null or nullable argument is passed to a method at the call-site. Even if the method itself is not annotated with @Nonnull. Of course, non-null semantics will always get enforced at compile-time irrespective of whether you use this annotation.

Unfortunately, the annotation is limited to method parameters. Local variables and fields are typically already in scope of the IDE analysis capabilities. Return types are out of scope. So your IDE will give you some hints regarding nullability, but it may not be complete. You can mitigate this by applying @Nonnull annotations. These would be redundant in the view of NullAway, but would provide new information to the IDE’s static analysis.

Configuring Error Prone and NullAway in Maven

An example configuration for Java 9 and up follows. You can also check the full pom.xml file.

Java 8 does not yet have the compiler plug-in infrastructure. The compiler plug-in must be configured differently to enable Error Prone on Java 8 code-bases. Refer to the Error Prone documentation for details.

The Maven configuration has the following characteristics:

And, finally, here is the full maven-compiler-plugin build plug-in section:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    <executions>
        <execution>
            <id>default-compile</id>
            <phase>compile</phase>
            <configuration>
                <compilerArgs>
                    <arg>-Xlint:all,-options</arg>
                    <arg>-Xdoclint:all/protected</arg>
                    <arg>-XDcompilePolicy=simple</arg>
                    <arg>-Xplugin:ErrorProne -Xep:NullAway:ERROR -XepOpt:NullAway:AnnotatedPackages=net.java.otr4j</arg>
                </compilerArgs>
            </configuration>
        </execution>
        <execution>
            <id>default-testCompile</id>
            <phase>test-compile</phase>
            <configuration>
                <compilerArgs>
                    <arg>-Xlint:all,-options,-deprecation</arg>
                    <arg>-Xdoclint:none</arg>
                </compilerArgs>
            </configuration>
        </execution>
    </executions>
    <configuration>
        <source>${project-java-version}</source>
        <target>${project-java-version}</target>
        <release>${project-java-version}</release>
        <showWarnings>true</showWarnings>
        <failOnWarning>true</failOnWarning>
        <annotationProcessorPaths>
            <path>
                <groupId>com.google.errorprone</groupId>
                <artifactId>error_prone_core</artifactId>
                <version>2.3.3</version>
            </path>
            <path>
                <groupId>com.uber.nullaway</groupId>
                <artifactId>nullaway</artifactId>
                <version>0.7.5</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>