Skip to content

Publish a Java 11 main artifact and a jdk21 classifier - #73

Merged
ghedwards merged 2 commits into
masterfrom
claude/jdk11-jdk21-artifacts
Sep 7, 2026
Merged

Publish a Java 11 main artifact and a jdk21 classifier#73
ghedwards merged 2 commits into
masterfrom
claude/jdk11-jdk21-artifacts

Conversation

@ghedwards

Copy link
Copy Markdown

Both modules now emit two jars from one source tree:

Artifact Class file Runs on
cfml.parsing-VERSION.jar 55 (Java 11) 11, 17, 21, 25
cfml.parsing-VERSION-jdk21.jar 65 (Java 21) 21+

Same for cfml.dictionary. The default is the one that works everywhere; the classifier is there for consumers who specifically want a 21-targeted build.

Nothing in the source required 21

Both modules compile clean at --release 11 — which checks the API surface, not just the language level, so it would catch a Java 12+ method call as well as a language feature. And no runtime dependency is above class file 55:

$ for each jar on CFLint's runtime classpath: report class file major > 55
  cfml.dictionary-2.16.1-SNAPSHOT.jar   major=65
  cfml.parsing-2.16.1-SNAPSHOT.jar      major=65
  (36 jars checked, nothing else)

The only Java 21 artifacts in the whole downstream classpath were cfparser's own. The baseline was raised in 5d21f6c, whose message states it broke Java 11 and 17 consumers. This restores them without giving up a 21 build.

The resource copy must run at process-classes

maven-jar-plugin takes a single classesDirectory, and classes-jdk21 holds only .class files — so the classified jar needs resources copied in or it ships without cfml.dictionary's 231 XML files.

That copy cannot move later than process-classes. Setting outputDirectory on a compiler execution leaks into what the reactor hands dependent modules, so cfml.parsing's tests resolve cfml.dictionary to classes-jdk21. Copy the resources at prepare-package instead and all 326 tests fail with:

java.lang.IllegalArgumentException: Problem loading dictionaryconfig.xml

which reads like a missing resource rather than a phase-ordering problem. I hit exactly that while building this, so the pom carries the explanation at the line where someone would otherwise move it.

skipIfEmpty stops the pom-packaged parent emitting and deploying a spurious cfparser-VERSION-jdk21.jar, which it did until that was added.

CI runs on both runtimes

gradle.yml gains a java-version axis of [11, 21] across all three operating systems, with fail-fast: false so one platform failing does not mask the others.

This matters more than it might look: compiling to 11 on a 21 JDK cannot catch a problem that only appears on an 11 runtime, and this environment has no Java 11 available and cannot download one — the proxy blocks Adoptium and foojay. The matrix is the only thing that actually exercises Java 11.

Gradle builds the Java 11 line only. Maven is what publishes, so the classifier is produced there.

Verification

  • 326 tests, ./gradlew build
  • CFLint 675 against the Java 11 parser — the real check that dropping the baseline is safe downstream
  • mvn deploy to a local file repository produces main, jdk21, sources and javadoc for both modules, and no parent jar
  • Both variants carry identical class and resource counts (cfml.dictionary: 20 classes, 231 XML each), differing only in bytecode level

Not verified here

I could not execute the suite on a real Java 11 JVM — hence the CI matrix. Until that runs, Java 11 support rests on bytecode level and API-surface checking, not on observed behaviour.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GzpZFd4rnE1Yi2sVHAji35


Generated by Claude Code

Both modules now emit two jars from one source tree: the main artifact at
class file 55 and a jdk21 classifier at 65. A Java 11 jar loads on 11, 17, 21
and 25, so the default is the one that works everywhere and the classifier is
there for consumers who want a 21-targeted build.

Nothing in the source required 21. Both modules compile clean at --release 11,
which checks the API surface and not just the language level, and no runtime
dependency is above class file 55 -- in CFLint's 36-jar classpath the only
Java 21 artifacts were cfparser's own two modules. The baseline was raised in
5d21f6c, whose message notes it broke Java 11 and 17 consumers; this restores
them without giving up a 21 build.

## The resource copy has to run at process-classes

maven-jar-plugin takes a single classesDirectory, and classes-jdk21 holds only
.class files, so the classified jar needs the resources copied in or it ships
without cfml.dictionary's XML.

That copy must not move later than process-classes. Setting outputDirectory on
a compiler execution leaks into what the reactor hands dependent modules, so
cfml.parsing's tests resolve cfml.dictionary to classes-jdk21. Copy the
resources at prepare-package instead and all 326 tests fail with "Problem
loading dictionaryconfig.xml", which reads like a missing resource rather than
a phase ordering problem. The pom says so at the point where it matters.

skipIfEmpty keeps the pom-packaged parent from emitting and deploying a
spurious cfparser-VERSION-jdk21.jar, which it did before that was added.

## CI now runs on both

gradle.yml gains a java-version axis of 11 and 21 across all three operating
systems. Compiling to 11 on a 21 JDK cannot catch a problem that only appears
on an 11 runtime, and this environment has no Java 11 available to test with,
so the matrix is the only thing that actually exercises it.

Gradle builds the Java 11 line only. Maven is what publishes, so the classifier
is produced there.

Verified: 326 tests, ./gradlew build, CFLint's 675 against the Java 11 parser,
and a deploy to a local file repository producing main, jdk21, sources and
javadoc jars for both modules with identical class and resource counts,
differing only in bytecode level.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GzpZFd4rnE1Yi2sVHAji35
The first attempt put Java 11 straight on the runner and all three 11 jobs
failed before compiling anything:

  UnsupportedClassVersionError: aQute/bnd/gradle/BndBuilderPlugin has been
  compiled by a more recent version of the Java Runtime (class file version
  61.0), this version of the Java Runtime only recognizes class file versions
  up to 55.0

The bnd OSGi plugin needs Java 17 or newer to load, so Gradle itself cannot
run on an 11 JVM. That is a build-tooling limit and says nothing about the
artifacts, which are Java 11 and were fine.

Both JDKs are now installed, with 21 last so it becomes JAVA_HOME and the
daemon keeps running there. -PjavaTestVersion selects the toolchain that
compiles and runs the tests, so the 11 leg exercises a real Java 11 runtime
while bnd still loads.

sourceCompatibility and targetCompatibility stay at 11 regardless of the
toolchain, so both legs test the same class file 55 output on two different
runtimes rather than testing two different builds.

Verified locally at -PjavaTestVersion=21 and with no property set, both
emitting major version 55. The 11 leg cannot be run here: no Java 11 is
installed and the proxy blocks Adoptium and foojay, which is the reason this
matrix exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GzpZFd4rnE1Yi2sVHAji35
@ghedwards
ghedwards merged commit 291ca9c into master Sep 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants