🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
// DOC-DERIVED — canonical BouncyCastle paste; class/package vary by library:
Exception in thread "main" java.lang.SecurityException: class "org.bouncycastle.jce.provider.BouncyCastleProvider"'s signer information does not match signer information of other classes in the same package
at java.base/java.lang.ClassLoader.checkCerts(ClassLoader.java:1148)
at java.base/java.lang.ClassLoader.defineClass2(Native Method)
at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:128)⚡ Quick Fix Works 80% of the time
Keep exactly one version of the signed library on the classpath — exclude every duplicate copy so one consistent set of signatures defines each package.
mvn -q dependency:tree -Dincludes=org.bouncycastle
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId> <!-- stale copy -->
</exclusion>
</exclusions>🧠 Why this Happens
Tap to expand the deep technical explanation
When the first class of a package loads, its code-signing certificates become that package’s registered signers within the classloader. Every subsequent class in the SAME package must present matching signatures; a class arriving with no signature or different certs means someone could have substituted code into a trusted namespace — exactly what the mechanism exists to prevent — so defineClass throws SecurityException instead of linking. Mixing bcprov 1.60 (signed by BC) with a shaded or rebuilt copy of one class from the same package guarantees the mismatch.
The HITEC City Parking Spot Analogy:
An office issues photo badges with holograms. One morning someone walks in wearing the same lanyard but a hand-drawn badge — security does not debate quality; anyone without the exact hologram is turned away for the whole floor’s safety.
🔁 How to Reproduce Confirm this is your error
Place two bouncycastle jars of different versions on the classpath (or re-sign one class of a signed package yourself) and load provider classes from both. DOC-DERIVED — wording frozen for decades.
🛠️ Solutions (5 Ways to Fix)
Deduplicate to a single version of the signed library
👉 Use this first — mixed versions inside one package are nearly always the cause.
dependency:tree lists every path to BouncyCastle artifacts; exclude all but your chosen release so one certificate set owns the whole package.
mvn -q dependency:tree -Dincludes=org.bouncycastle
# keep ONE of bcprov-jdk18on/bcprov-jdk15on, exclude siblings and old transitivesShade correctly: relocate packages AND strip signatures as a unit
👉 Use this when you must embed the library in a fat jar.
Partial META-INF stripping leaves some classes signed and others anonymous — the mismatch itself. Either keep the jar unshaded as an external dependency, or relocate the entire package to your namespace while removing ALL original signature files consistently.
<relocations>
<relocation>
<pattern>org.bouncycastle</pattern>
<shadedPattern>shaded.bc</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>Purge stray copies from server lib/endorsed directories
👉 Use this when the build tree is clean but servers still misbehave.
Tomcat lib/, WebSphere endorsed dirs, and old exploded wars often hold a second signed copy from years past. The runtime merges those with yours inside one loader — sweep them.
find $CATALINA_HOME -name "*bcprov*.jar" -o -name "*bcpkix*.jar"
# delete everything except the single version you standardize onVerify what you deploy with jarsigner
👉 Use this during diagnosis to see the actual certificate sets per jar.
jarsigner -verify -verbose prints each entry’s signing status; comparing outputs across your jars reveals whose entries are unsigned or differently certified before you ever run the app.
jarsigner -verify -verbose:summary bcprov-jdk18on-1.78.jar | head
jarsigner -verify target/app.jar | grep -i unsignedDisable signature checking via custom classloader
👉 DEV ONLY — never ship this; see below.
DEV ONLY. A classloader that overrides secure definition drops the integrity guarantee the error protects: any tampered class in a trusted package would now load silently. Useful only for a local experiment proving the diagnosis; production must fix packaging instead.
// DEV ONLY - diagnostic harness only
new ClassLoader(parent) {
@Override protected Class<?> loadClass(String name, boolean resolve) {
// skip checkCerts path by defining bytes directly
return findClass(name); // simplified, insecure
}
};📋 Version Notes
Package-signer consistency enforced identically since Java 1.2.
Unchanged; shaded fat jars increasingly common trigger.
Same check; message unchanged.
Unchanged.
🛡️ How to Prevent This Next Time
Ban duplicate packages across artifacts in CI, treat signed libraries as unshadable without full relocation, and record expected signing certificates for security-critical dependencies in the supply-chain checklist.