“There are risks and costs to action. But they are far less than the long range risks of comfortable inaction.”
John F. Kennedy
The Error Prone library from Google is a useful tool for detecting unnecessary Bug Patterns in your own sources. When converting some of my own libraries to Java 17, the Error Prone checks suddenly stopped working.
The problem was quickly identified, but fixing it was a real strain on the developer’s patience.
In order to find errors in the sources, Error Prone links directly into the Java compiler as an annotation processor. Since Java 17, however, access to classes from the JDK has been strictly protected. There have been restrictions on access since Java 9, but JEP 403: Strongly Encapsulate JDK Internals has put a stop to all simple accesses.
The custom libraries, which are checked by Error Prone, insert the library as an Annotation Processor into the Maven Compiler Plugin.
<compilerArgs> <arg>-XDcompilePolicy=simple</arg> <arg>-Xplugin:ErrorProne -XepPatchLocation:IN_PLACE</arg> </compilerArgs> <annotationProcessorPaths> <path> <groupId>com.google.errorprone</groupId> <artifactId>error_prone_core</artifactId> <version>${error-prone.version}</version> </path> <path> <groupId>de.schegge</groupId> <artifactId>error-prone-bug-patterns</artifactId> <version>0.2.1</version> </path> </annotationProcessorPaths>
In this example, Error Prone and two custom Bug Patterns from the Error Prone Bug Patterns library are included.
So that the Annotation Processor can use the JDK internal classes, the export mechanism of the Java module system must be used. With the command line option --add-exports $module/$package=$readingmodule
, a module $readingmodule
can access all public classes from the package $package
of the addressed module $module. If the constant ALL_UNNAMED
is used for $readingmodule
, then the public classes of the package can be accessed from the entire classpath.
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
All exports listed here must be specified for Error Prone to work correctly. In order not to have to specify these lines in the pom.xml
, the Maven developers came up with the file .mvn/jvm.configs
. When the JVM is startet, the parameters from the file are used, thus enabling all necessary access.
Patience was mostly tested by the Maven plugins in the Error Prone Bug Patterns project. Only the Java compiler had to be adapted in our own libraries. However, like Error Prone itself, this project accesses the JDK‘s internal classes. The .mvn/jvm.configs
is useless in this project. The compiler can only be encouraged to work correctly if the exports are passed via the compilerArgs
.
<compilerArgs> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg> <arg>--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg> </compilerArgs>
The Surefire plugin also needs to be reconfigured for the unit tests to run.
<argLine> --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED </argLine>
To upload the project to Maven Central, it requires a Javadoc artifact. The Javadoc plugin also accesses the internal classes and also requires advanced configuration.
<additionalOptions> --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED </additionalOptions>
Why the Apache Maven community has not yet found a meaningful standardization at this point may seem irritating at first. But if you consider that the latest version of the Maven Compiler Plugin still uses Java 8 as default, then it’s not surprising.
With the Maven adjustments it is now possible to publish the Error Prone Bug Patterns project on Maven Central. The next article on the topic of Error Prone will be about testing your own Bug Patterns.