简体   繁体   中英

Verify backward-compatibility on API level of a library built with a newer JDK

Recently, a teammate used the following function in our Java 8 code: Matcher.replaceAll(Function replacer) . The function was introduced in Java 9, but because he is using a newer compiler, the API function was simply found in the JDK's rt.jar and nobody noticed this won't work under real Java 8 environments. The compatibility settings are correctly set, and the gradle subproject has the following settings:

sourceCompatibility = 1.8
targetCompatibility = 1.8

I had very similar issues at the time when I first used the Java 6 function String.isEmpty in Java 5 code.

What can I do to enforce the usage of the correct API. As it is a shared library, do I have to use (and install, maintain..) a different JDK for this gradle subproject, or is there some kind of compatibility scanner which runs through a built jar and checks all rt references?

As you've noticed, the two compatibility configurations does not consider the APIs of older versions - only the syntax, semantics and the resulting byte code.

There are two options you can take. One is to have JDK 8 installed on your computer, and the configure Gradle to use it when compiling your project. It looks like this:

tasks.withType(JavaCompile) {
    options.fork = true
    options.forkOptions.executable = "$java8Home/bin/javac"
    options.bootstrapClasspath = files("$java8Home/jre/lib/rt.jar")
}

The disadvantage here is that you will need to have JDK 8 installed in the first place, and as it will probably be installed in different locations, you will need probably want to configure it with an environment variable or property (I've called it java8Home here).

However, since Java 9, the JDK now knows about the documented APIs of previous versions, and you can select which one to use with a new --release flag. This is not going to work if you use undocumented APIs, but it means you can compile your project with any versions of Java and still make the resulting classes compatible with Java 8. You can do it like this:

tasks.withType(JavaCompile) {
    if (JavaVersion.current() > JavaVersion.VERSION_1_8) {
        options.compilerArgs.addAll(['--release', '8'])
    }
}

Note that the 'if' statement is only there in case you still need to support running Gradle with Java 8 (through your JAVA_HOME variable). If you are only using later versions, it can be removed so you always set the 'compilerArgs'.

For some versions of Java, it is possible build Java code on a newer JDK to run on an older JDK / JRE. You have already discovered the --source and --target options for javac and the corresponding Gradle settings. The other thing you can do is to use --bootclasspath to tell javac to compile against the runtime libraries for an older version of Java.

Since you are using Gradle, check out "gradle-java-cross-compile-plugin" ( https://github.com/nebula-plugins/gradle-java-cross-compile-plugin ). I can't find any documentation for it, but it apparently deals with --target and --bootclasspath .


Having said that, I don't think cross-compiling Java is a good solution.

I would actually recommend that you set up a Continuous Integration (CI) server (eg Jenkins) with JDK installations for all of the Java versions you are interested in supporting. Then set up jobs to build your code and run your unit tests for each Java versions.

Note that simply compiling your code against the older Java libraries is not sufficient to verify backwards compatibility. Sometimes the behavior of libraries changes. You need to run your tests, and your tests need to cover the cases where compatibility issues may exist.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM