jOOQ is a very backwards compatible product. This doesn’t only mean that we keep our own API backwards compatible as well as possible, but we also still support Java 6 in our
commercial distributions.
In a previous blog post, I’ve shown how we manage to support Java 6 while at the same time not missing out on cool Java 8 language and API features, such as Stream and Optional support. For instance, you can do this with jOOQ’s ordinary distribution:
// Fetching 0 or 1 actors
Optional<Record2<String, String>> actor =
ctx.select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
.from(ACTOR)
.where(ACTOR.ID.eq(1))
.fetchOptional();
// Fetching a stream of actors
try (Stream<Record2<String, String>> actor = ctx
.select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
.from(ACTOR)
.fetchStream()) {
...
}
This API is present in jOOQ’s ordinary distribution and it is stripped from that distribution prior to building the Java 6 distribution.
But what about the JDK’s more subtle APIs?
It is relatively easy to remember not to use Streams, Optionals, lambdas, method references, default methods lightheartedly in your library’s code. After all, those were all major changes to Java 8 and we can easily add our API removal markers around those parts. And even if we forgot, building the Java 6 distribution would quite probably fail, because Streams are very often used with lambdas, in case of which a compiler that is configured for Java version 1.6 will not compile the code.
But recently,
we’ve had a more subtle bug, #6860. jOOQ API was calling
java.lang.reflect.Method.getParameterCount()
. Since we compile jOOQ’s Java 6 distribution with Java 8, this didn’t fail. The sources were kept Java 6
language compatible, but not
JDK 6 API compatible, and unfortunately, there’s no option in javac, nor in the Maven compiler plugin to do such a check.
Why not use Java 6 to compile the Java 6 distribution?
The reason why we’re using Java 8 to build jOOQ’s Java 6 distribution is the fact that Java 8 “fixed” a lot (and I mean a lot) of very old and weird edge cases related to generics,
overloading, varargs, and all that stuff. While this might be irrelevant for ordinary APIs, for jOOQ it is not.
We really push the limits of what’s possible with the Java language.
So, we’re paying a price for building jOOQ’s Java 6 distribution with Java 8. We’re flying in “stealth mode”, not 100% sure whether our JDK API usage is compliant.
Luckily, the JDK doesn’t change much between releases, so a lot of stuff from JDK 8 was already there in JDK 6. Also, our integration tests would fail, if we did accidentally use a method like the above. Unfortunately, that particular method call simply slipped by the integration tests (there will never be enough tests for every scenario).
The solution
Apart from fixing the trivial bug and avoiding that particular method, we’ve now added the cool “animal sniffer” Maven plugin to our Java 6 build, whose usage you can see here:
http://www.mojohaus.org/animal-sniffer/animal-sniffer-maven-plugin/usage.html
All we needed to add to our Java 6 distribution profile was this little snippet:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.16</version>
<executions>
<execution>
<phase>test</phase>
<goals>
<goal>check</goal>
</goals>
<configuration>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
<artifactId>java16</artifactId>
<version>1.1</version>
</signature>
</configuration>
</execution>
</executions>
</plugin>
This will then produce a validation error like the following:
[INFO] --- animal-sniffer-maven-plugin:1.16:check (default) @ jooq-codegen ---
[INFO] Checking unresolved references to org.codehaus.mojo.signature:java16:1.0
[ERROR] C:\..\JavaGenerator.java:232: Undefined reference: int java.lang.reflect.Method.getParameterCount()
[ERROR] C:\..\JavaGenerator.java:239: Undefined reference: int java.lang.reflect.Method.getParameterCount()
Perfect!
Like this:
Like Loading...
Thanks, this is great! For Gradle builds, it looks like https://plugins.gradle.org/plugin/ru.vyarus.animalsniffer works well.
I might be wrong, but I think that is exactly the purpose of the -Xbootclasspath option of javac. It allows you to specify which JDK classes to compile against.
But does it do that on a per-method level? E.g. in the article, where a new method was added to
java.lang.reflect.Method
. The class was there before…Yes, it does. It emits a compile error just as it would if you used the older JDK.
OK, cool. I didn’t know that! Thanks for sharing
You’re welcome. I didn’t know this plugin, this approach seems to be easier to configure. The other approach requires an rt.jar file on the machine you build on which makes it inconvenient to be honest. Thanks for the post! :)
If you build with Java 9 it have a cool feature “JEP 247: Compile for Older Platform Versions”, that can compile back to JDK6.
Yes indeed, so I’ve learned :)