Making JSR 305 Work On Java 9

Using annotations from JSR-305 (@Nonnull, @Nullable, etc.) with others from the javax.annotation package (@Generated, @PostConstruct) on Java 9 causes a split package. Here's the fix.

Do you use JSR 305 annotations like @Nonnull or @Nullable together with annotations from the javax.annotation package, for example @Generated or @PostConstruct? If so, then migrating to Java 9 won't be straight-forward due to split packages. Here's how to fix that problem.

What's the problem?

The Java Platform Module System doesn't like it when two modules contain types in the same package (totally random example: javax.annotation.Nonnull from JSR 305 and javax.annotation.Generated from the JDK). This is called a split package and I explain it in more detail in the Java 9 migration guide. All you need to know for now is how compiler and runtime react to it:

  • split between named modules (including platform, application, and automatic modules): the module system throws an error
module org.codefx.demo.java9_.jsr305_
	reads package javax.annotation from both jsr305 and java.annotation
  • split between named module (e.g. from the JDK) and the class path (which ends up in the unnamed module): class path portion of the package becomes invisible and compiler and runtime will complain of missing classes even though they are present on the class path
  • split between JARs on the class path: no problem, works as before

In which case you are depends on your setup. Let's step through the possibilities one by one and see which work and which don't. But before we come to that, there are two other things to talk about.

Alternatives To JSR 305

First I want to quote Stephen Connolly:

FTR there were never any annotations published by JSR 305

As a result, if you redistribute an Oracle JRE with the "annotations claimed to be JSR 305 but never actually officially published from the JSR spec page" then you are violating the Oracle JVM redistribution terms.

Please stop referring to these as JSR 305 annotations... at best they are "presumed JSR 305 annotations"

He's totally right and if you have the chance you should move away from the "presumed JSR 305 annotations". If it's null-safety you're after, I recommend using Optional or one of the other sets of annotations, for example those from SpotBugs, Eclipse (works outside the IDE with the Eclipse compiler), or JetBrains (code analysis can be run outside IntelliJ).

If moving away from these annotations is not an option for your project (at least not in the short term), you will unfortunately have to read on.

Platform Annotations

The other thing we have to talk about concerns the second actor in this drama: the platform module containing the javax.annotation package, namely java.xml.ws.annotation. This is a Java EE module and since Oracle got rid of Java EE (now Enterprise Eclipse for Java, EE4J), it does not resolve such modules by default. You'll have to use the --add-modules command line option to get it into the readability graph. We'll see shortly how that affects your situation.

With all of that settled, let's see where you're at.

Non-modular Project

Assuming you are just trying to get your code to compile on Java 9 you will be in the situation that your project is non-modular, i.e. it has no module declaration module-info.java.

Missing Platform Module

If you're simply running your Java 8 build on Java 9, you will likely see errors like this one:

$ javac
	--class-path deps/jsr305-3.0.2.jar
	-d build
	src/MixedJsr305AndJavaxAnnotation.java

> error: cannot find symbol
> import javax.annotation.Generated;
>                        ^
>     symbol:   class Generated
>     location: package javax.annotation

This has nothing to do yet with split packages. It just means that your code depends on the javax.annotation package that shipped with the JDK, but that the module containing it was not resolved. It needs to be added with --add-modules, which brings you to the next problem.

Invisible JSR-305 Annotations

Once you use --add-modules java.xml.ws.annotation to make sure java.xml.ws.annotation is part of the readability graph, the javax.annotation package is split between that module and the class path, which, as explained above, leads to the class path portion becoming invisible:

$ javac
	--class-path deps/jsr305-3.0.2.jar
	--add-modules java.xml.ws.annotation
	-d build
	src/MixedJsr305AndJavaxAnnotation.java


> error: cannot find symbol
> import javax.annotation.Nonnull;
>                        ^
>     symbol:   class Nonnull
>     location: package javax.annotation

(That error looks very similar to the last one, but now it's @Nonnull that can't be found.)

Patching The Platform Module

The hammer that hits all the split-package-nails is the --patch-module option. With it you can instruct the module system to pick some classes and stuff them into a module. Because it messes with module contents, it should be seen as a last resort.

$ javac
	--add-modules java.xml.ws.annotation
	--patch-module java.xml.ws.annotation=deps/jsr305-3.0.2.jar
	-d build
	src/org/codefx/demo/java9/jsr305/MixedJsr305AndJavaxAnnotation.java

Due to --patch-module the module system treats all classes in jsr305-3.0.2.jar as if they were in java.xml.ws.annotation. This mends the split and lets the code compile. During execution you need to repeat the --add-modules and --patch-module options.

This approach only works until java.xml.ws.annotation is removed in 2018

Note that this way jsr305-3.0.2.jar no longer needs to be on the class path. In this particular example it leads to --class-path becoming superfluous because no other dependency was needed.

This approach works, but only until java.xml.ws.annotation is removed from the JDK (likely in March or September 2018). Read on for a long-term solution.

Keeping The Split On The Class Path

As mentioned earlier, a split between JARs on the class path causes no problems. You can use this to your advantage by fulfilling your dependency on the JDK API with an external JAR, namely javax.annotation:javax.annotation-api. With it, the code can be compiled as follows:

$ javac
	--class-path deps/jsr305-3.0.2.jar:deps/javax.annotation-api-1.3.1.jar
	-d build
	src/org/codefx/demo/java9/jsr305/MixedJsr305AndJavaxAnnotation.java

This is the most elegant solution as it requires no hacking of the module system and no special command line options. Just adding a small dependency solves everything. At least until you try to modularize...

Modular Project

If you're modularizing your project, there's no good solution available. As far as I know there is no artifact that contains both parts of the split, i.e. the content of both javax.annotation-api-1.3.1.jar and jsr305-3.0.2.jar. Taken together:

  • you need two artifacts to fulfill these dependencies
  • your module declaration needs to require them in order for your code to be able to use them
  • requiring them means that they must be named modules (explicit or automatic ones)
  • you have two named modules that split a package

This leaves you with picking one of these two artifacts (requiring it as an automatic module until it's modularized) and patching the other one into it.

Alternatively you could create an artifact that combines the other two and make it available in your organization's repository (e.g. Sonatype's Nexus). It would be nice to upload that to Maven Central, but I'm afraid there could be licensing issues with publishing code in the javax.* packages (I don't actually know, though).

Reflection

If you use JSR-305 annotations (e.g. @Nonnull or @Nullable) together with annotations from the javax.annotation package (e.g. @Generated or @PostConstruct), then running on Java 9 will likely create a split package that you need to work around.

As long as your code is non-modular, the best solution is to explicitly require both jsr305 and javax.annotation-api, so they both end up on the class path, where package splits don't matter. If you modularize your code, there is no way around --patch-module unless you want to create your own artifact combining the other two.