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.