I have a project that uses a lot of reflection, also on “new” Java features such as records and sealed classes. I’m writing a class like this:
public class RecordHelper { public static boolean isRecord(Class<?> type) { return type.isRecord(); } }
Of course, this only works in Java 16 and higher, so I’m trying to set up a multi-release JAR file, with a default implementation like this:
public class RecordHelper { public static boolean isRecord(Class<?> type) { return false; } }
I’ve been able to set that up with Maven using this post on Baeldung, and it works great. It’s nice, because it doesn’t rely on separate profiles for various versions, which keeps my pom file clean.
But now I need to write tests.
I want to be able to run the test suite on all platforms that I want to support, which means everything from JDK 8 and up. (I don’t mind compiling on a different JDK before I run the tests.) Of course, on JDK 16 and up, I also want to test records-related things (and on 17 and up, sealed classes), so that means that I have to compile some records, which means I will inevitably have some class files that won’t work on older JDKs.
In my mind, it would make sense to have something like a multi-release JAR file for tests as well, where the records tests get placed in the appropriate place in META-INF/versions
, but of course tests aren’t usually packaged in a JAR, so that doesn’t work.
Is there a way to get this working in a single-module Maven project without too much repetition?
Of course, it would have to ‘accumulate’ test classes as the JDK version goes up, e.g. on JDK 8 I only have the ‘regular’ test classes, on JDK 16 I have the regular ones and the java16
ones, and on JDK 17 I have the regular ones, the java16
ones and the java17
ones. I haven’t found a way yet to express this kind of thing in Maven in a concise way, but I’m not a Maven expert.
Or am I looking in the wrong direction, and is it preferable to make a multi-module Maven project, with the main code in one module, and the tests in another, and then generate a multi-module jar for the tests as well? If so, how would I run this jar file on different JDKs?
Advertisement
Answer
I ended up creating a multi-module build, as @khmarbaise suggests in one of the comments below the question. He also provides an example.
With modules named core
, m16
, m17
, etc. I can place the bulk of the tests in the core
module, and the JDK specific tests in the corresponding modules. I can even test the integration of the JDK-specific code with the rest of the core
module, because apparently in this setup, Maven picks the implementation of a class in m17
over the implementation of the same class in core
, thereby simulating what happens in a real multi-release jar file. I’m not 100% certain if this is programming by coincidence or not, but it works ð
Another advantage of a multi-module approach, is that if I use new language features in the JDK-specific submodules, my IDE will just understand it. With the approach in the Baeldung article I cited in the question, that doesn’t work and I see squiggles everywhere.
In order to test the build on different JDKs, I use profiles that activate on the JDK version, and that select appropriate submodules, like this:
<profile> <id>modules-jdk8</id> <activation> <jdk>[1.8,11)</jdk> </activation> <modules> <module>core</module> </modules> </profile> <profile> <id>modules-jdk16</id> <activation> <jdk>[16,17)</jdk> </activation> <modules> <module>core</module> <module>m16</module> </modules> </profile> <profile> <id>modules-jdk17</id> <activation> <jdk>[17,)</jdk> </activation> <modules> <module>core</module> <module>m16</module> <module>m17</module> <module>release</module> </modules> </profile>
(Maybe an approach using Toolchains would have been better.)
Note that there must also be a release
module, which has dependencies on all other modules, and which uses the Maven Assembly plugin to actually create the jar file, and which places the JDK-specific classes in the appropriate directories in the jar.
I have added a <maven.install>true</maven.install>
property to all modules except the release
module, so that only the actual multi-release jar file will be installed into my local repostory. This is important, because I also want to publish to Maven Central, and I don’t want to litter it with all kinds of half-product jar files.