Repository navigation
Allow building with a Java 21 toolchain while targeting Java 17 - #532
tonygermano wants to merge 3 commits into
Conversation
JaCoCo 0.8.8 cannot read class files newer than Java 18. On a newer JDK it logs a stack trace for each JDK class it fails to instrument, such as CLDRLocaleDataMetaInfo on Java 21. 0.8.15 officially supports up to Java 26. There are no API changes since 0.8.8. JaCoCo now depends on ASM 9.10.1 instead of 9.2. That ASM is only on JaCoCo's own classpath and does not change the ASM version the distribution ships. The verification metadata was regenerated with a cold cache, and the entries for the old versions were removed. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Tony Germano <tony@germano.name>
KeyEncryptorTest only referenced com.sun.crypto.provider.SunJCE to get its class name, which the encryption settings take as a string. Use the name directly. The other packages exported to the server test compile only appear in string literals, so the test compile needs no --add-modules or --add-exports. This prepares for compiling with --release, which javac does not allow together with --add-exports of system modules. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Tony Germano <tony@germano.name>
NicoPiel
left a comment
There was a problem hiding this comment.
LGTM. A few non-blocking notes:
userApiJavadocup-to-date check: the JDK used isn't a task input, so after switching between 17 and 21 the task can be marked up to date and keep javadoc from the other version. Maybe addinputs.property('javaToolchain', rootProject.javaToolchain). Release builds use 17, so this is minor.- CONTRIBUTING wording: "the version pinned in
.sdkmanrc" suggests the build uses that exact JDK, but the toolchain only sets the version, not the vendor. Gradle can pick any installed 17, not necessarily the pinned zulu-fx. That was true before this PR; just a wording nit.
|
But why? Can we just take the plunge? Is there a reason to run parallel? (And for that matter, why 21 instead of 25)? |
|
True. If we're getting rid of JFX too, why not upgrade to 25 directly? |
|
@mgaffigan It says in the PR description
I was just trying to have a targeted PR that allows for different toolchain and release versions. Upgrading Gradle to 9.x was more than I wanted to handle in a single PR. For the next release I'd like to get to allowing a 25 toolchain to build for release 21, but if for some reason we don't, at least the framework is in place. |
|
Jumping to Java 25 is not substantially larger. I suggest we take the leap rather than adding more build options. See https://github.com/OpenIntegrationEngine/engine/pull/536/changes |
Compile with --release 17, so the output keeps Java 17 bytecode and API usage whichever JDK compiles it. The toolchain still defaults to 17 and can be overridden with -PjavaToolchain=21, which also selects the JDK the tests run on. Java 21 works with the current Gradle and test libraries. Newer JDKs need a Gradle upgrade. userApiJavadoc now runs the toolchain's javadoc instead of the one from the JDK running Gradle, and its version is a task input, so switching toolchains regenerates the docs. CONTRIBUTING.md describes the new option. Compared with the JDK 17 toolchain, a build with the JDK 21 toolchain produces only Java 17 (major version 61) class files. Of the 3,640 classes, 1,487 are byte-identical. The vocab classes, which build does not compile, are all byte-identical. The rest differ only in ways that come from newer javac versions and do not change behavior: - Metadata: constant pool order, MethodParameters attributes for the mandated and synthetic parameters of inner class constructors, and argN parameter names in the LocalVariableTable of anonymous class constructors. - Object methods called on an interface type (toString, equals, getClass) are emitted as invokeinterface on the interface instead of invokevirtual on Object (25 classes). The JVM resolves both to the same method. - A switch on an enum inside that enum's own class uses ordinal() directly instead of a $SwitchMap. This drops two synthetic classes, MetaDataColumnType$1 and ContextType$1, and three switch map fields from other synthetic classes. Closes OpenIntegrationEngine#215. Issue: OpenIntegrationEngine#215 Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Tony Germano <tony@germano.name>
e015981 to
de69da8
Compare
@mgaffigan I don't think the two PRs are as far apart as they look. #536 also splits the release and toolchain versions (toolchain 25, As far as I can tell, the real difference is whether the toolchain can be overridden with a build property. If that's the point of contention, let's work it out here rather than replacing one PR with the other. My reason for wanting it: with I'd suggest building #536 on top of this PR instead, as the fix for #306. After this merges, moving to 21/25 is a change to 🤖 Drafted with Claude Code |
@NicoPiel I pushed a change addressing both of these items. |
Yes - it is my position that we take too few requirements. Options = risk, and most of the time YANGI. Honestly: I kept the 21 backcompat on the 25 tree by influence from this PR, but if I were the reviewer instead of the author, I'd be arguing against allowing 21 after the 25 upgrade. If you want to run 21, use 4.7.0. If you want 4.7.1, it requires 25. |
|
I will also say that 4x PR's for a 250 line change is a lot of approvals. I still think the entire jump can be a single PR. I'm not seeing a lot of work required here. |
|
So wanting to remove the build property has more to do with the fact that you don't think we should support more than one LTS release of Java? It's true that in that case we wouldn't need the build property. We could also add the higher toolchain version to I think several of the PRs lately have been too big. I really like the occasional ones that I open and quickly can tell what it does without asking an AI to explain it to me. I'm not sure specifically what you are referring to with 4x PRs for 250 lines. I already have 3 different but loosely related items in this PR as separate commits. |
|
I honestly don't understand how javafx relates to this either. It's not required to build because we have |
Allow building with a Java 21 toolchain while targeting Java 17
Branch:
tony:build/java21-toolchain→mainSummary
This PR makes it possible to compile and test with a newer JDK while still shipping Java 17 bytecode and staying within the Java 17 API. The default build is unchanged: it still uses a Java 17 toolchain.
To make that the default on one machine, add
javaToolchain=21to~/.gradle/gradle.properties.Java 21 works with the current Gradle (8.14) and test libraries. Java 25 isn't covered here because it needs Gradle 9.1+ and a newer Byte Buddy for Mockito.
The PR has three commits:
CLDRLocaleDataMetaInfo. 0.8.15 officially supports up to Java 26 and has no API changes since 0.8.8. Its ASM 9.10.1 (replacing 9.2) is only on JaCoCo's own classpath and doesn't change the ASM we ship.gradle/verification-metadata.xmlwas regenerated with a cold cache, and the entries for the old versions were removed.--add-exportsof system modules together with--release.KeyEncryptorTestonly importedcom.sun.crypto.provider.SunJCEto get its class name, which the encryption settings take as a string. It now uses the name as a constant. The other exported packages only appeared in string literals, so thecompileTestJavablock inserver/build.gradleis removed.JavaCompiletasks now useoptions.release = 17, so the output targets Java 17 whichever JDK compiles it. The toolchain defaults to the release version and can be overridden with-PjavaToolchain. The tests run on the same JDK.userApiJavadocnow runs the toolchain'sjavadocinstead of the one from the JDK running Gradle, and the javadoc version is now a task input, so switching toolchains regenerates the docs. CONTRIBUTING.md describes the new option.Closes #215.
Testing
Claude checked
./gradlew clean build -Pcoverage=true -PdisableSigning=truepassed before each commit. At the tip it passes with both the 17 and 21 toolchains, and all 768 tests pass.MethodParametersattributes for the mandated and synthetic parameters of inner class constructors, andargNnames in theLocalVariableTableof anonymous class constructors.Objectmethods (toString,equals,getClass) called on an interface type are emitted asinvokeinterfaceon the interface instead ofinvokevirtualonObject. The JVM resolves both to the same method.ordinal()directly instead of a$SwitchMap. This drops two synthetic classes (MetaDataColumnType$1andContextType$1) and three switch map fields from other synthetic classes.🤖 Drafted with Claude Code