Skip to content

Ship the Java 10 shim in META-INF/versions/10 so Java 8 builds accept the jar - #516

Merged
jmanico merged 7 commits into
mainfrom
java8-bytecode-515
Oct 11, 2026
Merged

jmanico merged 7 commits into
mainfrom
java8-bytecode-515

Conversation

@jmanico

@jmanico jmanico commented Oct 11, 2026

Copy link
Copy Markdown
Member

Problem

Since 20260921.1 bundled the shims into the sanitizer jar, its root has held two classes compiled for Java 10: org.owasp.shim.ForJava10AndLater and the empty org.owasp.shim.Notice. A Java 8 project that checks its dependencies with the Maven Enforcer rule enforceBytecodeVersion at maxJdkVersion 1.8 fails on the jar unless it lists both in ignoreClasses (#515).

Fix

  • java10-shim/pom.xml: a second compiler execution compiles ForJava10AndLater into target/classes/META-INF/versions/10/.
  • Shade: from java10-shim it takes only META-INF/versions/10/**. The jar is already Multi-Release: true for its module descriptor, so Java 10+ finds the shim there, while Java 8 and 9 never look. The enforcer rule doesn't look there either: it allows release N under META-INF/versions/N/ (source). Notice is no longer shipped.
  • The root copy stays in java10-shim's own output. With mvn test, the reactor's test class paths read that module from a directory, where versioned entries don't apply. Keeping the root copy means mvn test and mvn verify still run the same shim, which examples/pom.xml relies on.
  • Notice.java stays in the source tree. It is java10-shim's only public class, and the publication profile builds Javadoc for every module.

Which shim each runtime uses is unchanged:

Runtime Before After
Java 8 ForJava8 (the class fails to load) ForJava8 (the class isn't visible)
Java 9 ForJava8 ForJava8
Java 10+, classpath or module path ForJava10AndLater ForJava10AndLater

The one difference is in setups that ignore multi-release jars, such as OSGi frameworks before R7 or a consumer's repackaged jar that drops the header. On any Java version, those now use ForJava8, the implementation Java 8 users already run.

Regression checks

  • src/it/java8-consumer/Java8ConsumerCheck is a Java 8 program that verify compiles and runs against the packaged jar on the class path at integration-test, next to the JPMS consumer. It fails if:
    • any class outside META-INF/versions/ is newer than Java 8, or any class under META-INF/versions/N/ is newer than N;
    • the sanitizer doesn't pick the shim meant for the running JDK;
    • or the sanitizer doesn't produce the expected output.
  • A new CI job, java8-runtime, packages with JDK 11 and runs the same check on a Java 8 runtime. Until now CI never loaded the jar on Java 8.

Verification

  • ./mvnw -o -ntp -B clean verify passes on JDK 11, 17, 21 and 25. On each, the check reports 140 classes within bounds and ForJava10AndLater selected.
  • The new check fails on main: org/owasp/shim/ForJava10AndLater.class is compiled for Java 10 but is outside META-INF/versions/, and the same for Notice.class.
  • The reporter's rule (enforceBytecodeVersion, maxJdkVersion 1.8, no ignoreClasses, extra-enforcer-rules 1.10.0) fails on the released 20260924.2 jar and passes on this branch's jar.
  • A copy of the branch jar with the Multi-Release header removed fails the check on JDK 21 (loaded org.owasp.shim.ForJava8 rather than org.owasp.shim.ForJava10AndLater). So a silent fallback can't slip through.
  • I had no Java 8 runtime locally, so the Java 8 path is proven only by the new CI job.

Also updated: change_log.md (adds a * Next release entry), docs/maven.md, the java10-shim and library READMEs, the loader comment in Java8Shim, and one rule in AGENTS.md: every class outside META-INF/versions/ must be Java 8 bytecode.

Closes #515

🤖 Generated with Claude Code

jmanico and others added 7 commits October 11, 2026 12:46
Since 20260921.1 bundled the shims into the sanitizer jar, its root held
two classes compiled for Java 10, org.owasp.shim.ForJava10AndLater and the
empty org.owasp.shim.Notice.  A Java 8 project that checks its
dependencies with the Maven Enforcer rule enforceBytecodeVersion at
maxJdkVersion 1.8 failed on the jar unless it listed both in
ignoreClasses.

java10-shim now compiles ForJava10AndLater a second time, into
META-INF/versions/10/, and the shade configuration takes only that copy
from the module.  The jar is already multi-release for its module
descriptor, so Java 10 and later find the shim there and Java 8 and 9,
like the enforcer rule, never look.  The root copy stays in java10-shim's
own output because the reactor's test class paths read it from a
directory, where versioned entries do not apply, so `mvn test` and
`mvn verify` still run the same shim.

src/it/java8-consumer is a Java 8 program the build runs against the
packaged jar at integration-test.  It fails if any class is newer than
Java 8 outside META-INF/versions/, or newer than N inside
META-INF/versions/N/, or if the sanitizer does not pick the shim meant
for the running JDK.  On the old jar it names the two Java 10 classes; on
a jar without the Multi-Release header it reports the Java 8 fallback
on Java 21.  A new CI job packages with JDK 11 and runs it on a Java 8
runtime, the first time CI loads the jar on Java 8.

Closes #515.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Java 8's javac fails with 'directory not found' when the -d directory
does not exist; later javac versions create it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bnd comment on the Multi-Release header said the jar is
multi-release only for its module descriptor and holds no versioned
class.  Since the previous commits it also holds ForJava10AndLater in
META-INF/versions/10/, and without the header every runtime falls back
to ForJava8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Java8ConsumerCheck checked that Sanitizers and Java8Shim came from the
jar under test, but not the shim Java8Shim picked, which is the class the
versioned directory exists for.  With java10-shim/target/classes ahead of
the jar on the class path the check passed, on the root copy of
ForJava10AndLater, and now fails:

  org.owasp.shim.ForJava10AndLater loaded from .../java10-shim/target/classes

The build and CI put nothing else on the class path, so no run so far
was affected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shade filter named META-INF/versions/10/, while java10-shim's
release comes from maven.compiler.release.  Had that property moved, the
versioned compile would have landed elsewhere and the filter would have
silently shipped no shim; only Java8ConsumerCheck's selection check would
have noticed.  Take META-INF/versions/** instead.  The shaded jar is
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JpmsConsumerCheck now loads ForJava10AndLater, uninitialized, through the
sanitizer module's class loader on Java 10 and later and checks it is in
that module, so the build shows the versioned entry is read on the module
path as well as on the class path.  A copy of the jar without
META-INF/versions/10/ fails with

  the module has no ForJava10AndLater on Java 21

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
'A `ignoreClasses` workaround' becomes 'An'.  The change log and
docs/maven.md said enforceBytecodeVersion allows the versioned shim; it
does so in its default mode, and with strict set it holds versioned
classes to maxJdkVersion too, so say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jmanico

jmanico commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

Review pass

An independent review (verdict: merge after small fixes) found nothing functional wrong. It raised one should-fix and five nits. Each is fixed in its own commit:

# Finding Commit Fix
1 bnd comment on Multi-Release still said the jar holds no versioned class (should-fix) 422e87c Comment now names both reasons for the header: the Java 9 descriptor and the Java 10 shim
2 "A ignoreClasses workaround" 8582d4e "An"
3 Java8ConsumerCheck didn't check where the selected shim came from 11c3cd0 The selected shim class is now in the code-source loop. With java10-shim/target/classes ahead of the jar, the check passed before and now fails with ForJava10AndLater loaded from …/java10-shim/target/classes
4 Shade filter hard-coded versions/10 while the shim's release comes from a property 7b4cbfa Filter takes META-INF/versions/**. The jar is unchanged
5 Docs implied the enforcer rule always allows the versioned shim 8582d4e change_log.md and docs/maven.md now say this holds in its default mode, and that strict holds versioned classes to maxJdkVersion too
6 Optional: the JPMS check didn't prove the versioned shim is reachable on the module path 6a88fb5 On Java 10+ it loads ForJava10AndLater through the module's class loader and checks it is in the module. A jar without META-INF/versions/10/ fails with the module has no ForJava10AndLater on Java 21

The review also checked, by running them: the shaded jar's entries and dependency-reduced POM, the reporter's real enforcer rule, shim selection on JDK 11/17/21 in four class-path and module-path shapes, mvn test vs verify, jar --validate, a local -Ppublication deploy to target/staging-deploy (Javadoc builds for every module, which is why Notice.java stays), and the release workflow's * Next release substitution.

After the fixes, ./mvnw -o -ntp -B clean verify passes on JDK 11, 17, 21 and 25 with identical jar contents. CI is green on 8582d4e, including java8-runtime (on Java 8 the sanitizer uses org.owasp.shim.ForJava8).

🤖 Generated with Claude Code

@jmanico
jmanico merged commit a6f0177 into main Oct 11, 2026
8 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.

The integration of shim java 8 and 10 breaks by with Maven Enforcer Plugin to verify java 8

1 participant