Do you use JSR 305 annotations like
@Nullable together with annotations from the
javax.annotation package, for example
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".
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.
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.
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
▚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
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
--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
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
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...
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
- 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).
If you use JSR-305 annotations (e.g.
@Nullable) together with annotations from the
javax.annotation package (e.g.
@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
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.