Summary
bctls-jdk18on version 1.86 ships a broken multi-release JAR. On any JDK 9 or
newer, creating an SSLEngine from the BCJSSE provider fails with:
java.lang.NoSuchMethodError: 'javax.net.ssl.SSLEngine
org.bouncycastle.jsse.provider.SSLEngineUtil.create(org.bouncycastle.jsse.provider.ContextData)'
at org.bouncycastle.jsse.provider.ProvSSLContextSpi.engineCreateSSLEngine(Unknown Source)
at java.base/javax.net.ssl.SSLContext.createSSLEngine(SSLContext.java:373)
The regression is a packaging problem, not a source problem: the versioned
(META-INF/versions/9/) copy of ProvSSLContextSpi that existed in 1.85 is
missing from the 1.86 JAR, while the versioned copy of SSLEngineUtil
(whose create(...) return type differs from the base copy) is still present.
That leaves the base ProvSSLContextSpi linked against a method signature that
does not exist in the versioned SSLEngineUtil the JVM actually loads.
- Affected artifact:
org.bouncycastle:bctls-jdk18on:1.86
- Last working version:
org.bouncycastle:bctls-jdk18on:1.85
- Impact: BCJSSE is unusable on JDK 9+ (
SSLContext.createSSLEngine() throws)
Environment
- JDK: Temurin 21.0.9+10 (reproduces on any JDK ≥ 9)
- OS: Linux
- Artifacts:
bcprov-jdk18on:1.86, bcutil-jdk18on:1.86, bctls-jdk18on:1.86
Minimal reproducer
No third-party framework required — just the three BC 1.86 JARs on the classpath:
import java.security.*;
import javax.net.ssl.*;
public class Repro {
public static void main(String[] a) throws Exception {
Security.addProvider((Provider) Class
.forName("org.bouncycastle.jce.provider.BouncyCastleProvider")
.getDeclaredConstructor().newInstance());
Security.addProvider((Provider) Class
.forName("org.bouncycastle.jsse.provider.BouncyCastleJsseProvider")
.getDeclaredConstructor().newInstance());
SSLContext ctx = SSLContext.getInstance("TLS", "BCJSSE");
ctx.init(null, null, null);
ctx.createSSLEngine(); // <-- throws NoSuchMethodError on JDK 9+
System.out.println("OK");
}
}
Run:
javac -cp bcprov-jdk18on-1.86.jar:bctls-jdk18on-1.86.jar:bcutil-jdk18on-1.86.jar Repro.java
java -cp .:bcprov-jdk18on-1.86.jar:bctls-jdk18on-1.86.jar:bcutil-jdk18on-1.86.jar Repro
1.86 throws NoSuchMethodError; the same program with 1.85 prints OK.
Root cause analysis
bctls-jdk18on is a multi-release JAR (Multi-Release: true). On JDK ≥ 9 the
JVM prefers class files under META-INF/versions/9/ over the base copies.
Two relevant classes:
1. SSLEngineUtil
The base and versioned copies declare a different return type for
create(ContextData):
# base org/bouncycastle/jsse/provider/SSLEngineUtil.class
static javax.net.ssl.SSLEngine create(org.bouncycastle.jsse.provider.ContextData);
# META-INF/versions/9/org/bouncycastle/jsse/provider/SSLEngineUtil.class
static org.bouncycastle.jsse.provider.ProvSSLEngine create(org.bouncycastle.jsse.provider.ContextData);
(This return-type difference exists in 1.85 too and is fine on its own —
ProvSSLEngine is a subtype of SSLEngine. The problem is the caller below.)
2. ProvSSLContextSpi
Its engineCreateSSLEngine() invokes SSLEngineUtil.create(ContextData).
Because invokestatic is resolved by name and full descriptor including the
return type, the caller's descriptor must match the callee that the JVM loads.
# base ProvSSLContextSpi.engineCreateSSLEngine() bytecode
invokestatic SSLEngineUtil.create:(Lorg/.../ContextData;)Ljavax/net/ssl/SSLEngine;
The base ProvSSLContextSpi is compiled against the base SSLEngineUtil
(return SSLEngine). It must therefore be paired with the base SSLEngineUtil,
or shipped with a matching versioned copy compiled against the versioned
SSLEngineUtil (return ProvSSLEngine).
The packaging regression
| Entry |
1.85 (works) |
1.86 (broken) |
META-INF/versions/9/.../SSLEngineUtil.class |
present |
present |
META-INF/versions/9/.../ProvSSLContextSpi.class |
present (24239 bytes) |
MISSING |
total META-INF/versions/**/*.class entries |
452 |
23 |
In 1.85, the JVM loads both versioned copies, which are compiled against
each other — consistent, links fine.
In 1.86, the versioned ProvSSLContextSpi was dropped, so the JVM loads:
- versioned
SSLEngineUtil → create(ContextData) returns ProvSSLEngine
- base
ProvSSLContextSpi → calls create(ContextData) expecting return SSLEngine
No method with descriptor (ContextData)Ljavax/net/ssl/SSLEngine; exists in the
loaded SSLEngineUtil → NoSuchMethodError.
The overall count of versioned classes collapsed from 452 to 23 between the two
releases, which suggests the multi-release layer was regenerated/pruned and a
number of META-INF/versions/9/... classes (including ProvSSLContextSpi) were
unintentionally omitted.
Summary
bctls-jdk18onversion 1.86 ships a broken multi-release JAR. On any JDK 9 ornewer, creating an
SSLEnginefrom the BCJSSE provider fails with:The regression is a packaging problem, not a source problem: the versioned
(
META-INF/versions/9/) copy ofProvSSLContextSpithat existed in 1.85 ismissing from the 1.86 JAR, while the versioned copy of
SSLEngineUtil(whose
create(...)return type differs from the base copy) is still present.That leaves the base
ProvSSLContextSpilinked against a method signature thatdoes not exist in the versioned
SSLEngineUtilthe JVM actually loads.org.bouncycastle:bctls-jdk18on:1.86org.bouncycastle:bctls-jdk18on:1.85SSLContext.createSSLEngine()throws)Environment
bcprov-jdk18on:1.86,bcutil-jdk18on:1.86,bctls-jdk18on:1.86Minimal reproducer
No third-party framework required — just the three BC 1.86 JARs on the classpath:
Run:
1.86throwsNoSuchMethodError; the same program with1.85printsOK.Root cause analysis
bctls-jdk18onis a multi-release JAR (Multi-Release: true). On JDK ≥ 9 theJVM prefers class files under
META-INF/versions/9/over the base copies.Two relevant classes:
1.
SSLEngineUtilThe base and versioned copies declare a different return type for
create(ContextData):(This return-type difference exists in 1.85 too and is fine on its own —
ProvSSLEngineis a subtype ofSSLEngine. The problem is the caller below.)2.
ProvSSLContextSpiIts
engineCreateSSLEngine()invokesSSLEngineUtil.create(ContextData).Because
invokestaticis resolved by name and full descriptor including thereturn type, the caller's descriptor must match the callee that the JVM loads.
The base
ProvSSLContextSpiis compiled against the baseSSLEngineUtil(return
SSLEngine). It must therefore be paired with the baseSSLEngineUtil,or shipped with a matching versioned copy compiled against the versioned
SSLEngineUtil(returnProvSSLEngine).The packaging regression
META-INF/versions/9/.../SSLEngineUtil.classMETA-INF/versions/9/.../ProvSSLContextSpi.classMETA-INF/versions/**/*.classentriesIn 1.85, the JVM loads both versioned copies, which are compiled against
each other — consistent, links fine.
In 1.86, the versioned
ProvSSLContextSpiwas dropped, so the JVM loads:SSLEngineUtil→create(ContextData)returnsProvSSLEngineProvSSLContextSpi→ callscreate(ContextData)expecting returnSSLEngineNo method with descriptor
(ContextData)Ljavax/net/ssl/SSLEngine;exists in theloaded
SSLEngineUtil→NoSuchMethodError.The overall count of versioned classes collapsed from 452 to 23 between the two
releases, which suggests the multi-release layer was regenerated/pruned and a
number of
META-INF/versions/9/...classes (includingProvSSLContextSpi) wereunintentionally omitted.