Issue Summary
When using Strata as a dependency in applications with complex classloader hierarchies (application servers, OSGi containers, multi-module applications), ExtendedEnum initialization fails with ClassNotFoundException when first accessed from ForkJoinPool worker threads or parallel streams.
Stack Trace
Failed to load ExtendedEnum for interface com.opengamma.strata.basics.date.DayCount:
java.lang.IllegalArgumentException: Unable to find enum provider class:
com.opengamma.strata.basics.date.StandardDayCounts
at com.opengamma.strata.collect.named.ExtendedEnum.parseProviders(ExtendedEnum.java:176)
Caused by: java.lang.ClassNotFoundException:
com.opengamma.strata.basics.date.StandardDayCounts
at org.joda.convert.RenameHandler.lookupType(RenameHandler.java:197)
at com.opengamma.strata.collect.named.ExtendedEnum.parseProviders(ExtendedEnum.java:174)
Root Cause
Current Implementation (ExtendedEnum.java:174):
cls = RenameHandler.INSTANCE.lookupType(key);
RenameHandler.lookupType() uses the thread context classloader (TCCL) as its primary mechanism to load classes. When this is unavailable or incorrect, it falls back to the classloader that loaded RenameHandler itself.
Problem in Production Environments:
In complex classloader hierarchies (common in application servers, OSGi, etc.):
-
Common/Shared ClassLoader (parent)
- Contains: Joda-Convert (where
RenameHandler lives), Joda-Beans, Guava
- Does NOT contain: Strata classes
-
Application ClassLoader (child)
- Contains: Strata JARs with
ExtendedEnum and provider classes like StandardDayCounts
- Can see parent classes via delegation
-
ForkJoinPool Worker Threads
- Thread Context ClassLoader (TCCL):
null or incorrect
When ExtendedEnum (child) calls RenameHandler.lookupType() (parent):
- TCCL is
null → skipped
RenameHandler's classloader is the parent → cannot see StandardDayCounts in child
- Result:
ClassNotFoundException
Reproduction
The issue can be reproduced using a standalone Java program that simulates production classloader hierarchy (attaching it in a comment - unzip it at the project root):
cd standalone-reproducer
./run-production-sim.sh
Before Fix:
Caused by: java.lang.ClassNotFoundException: com.opengamma.strata.basics.date.StandardDayCounts
at org.joda.convert.RenameHandler.lookupType(RenameHandler.java:197)
at com.opengamma.strata.collect.named.ExtendedEnum.parseProviders(ExtendedEnum.java:174)
Why Maven Tests Cannot be Used to Reproduce:
Maven's Surefire plugin uses a simple classloader hierarchy where all classes (Joda-Convert, Strata, test classes) are loaded by the same classloader. This allows RenameHandler's fallback mechanisms to succeed. The issue only manifests in production environments with parent/child classloader separation.
Proposed Solution
Replace RenameHandler.INSTANCE.lookupType() with Class.forName() using an explicit classloader:
cls = Class.forName(key, true, ExtendedEnum.class.getClassLoader());
Why This Works:
- Uses the classloader that loaded
ExtendedEnum (the child/application classloader)
- This classloader has visibility to all provider classes
- Thread-independent: doesn't rely on TCCL
- Follows standard Java classloading best practices
Files to Change:
ExtendedEnum.parseProviders() - line 174
CombinedExtendedEnum.parseChildren() - line 97
Benefits:
- More explicit and predictable classloading
- Thread-independent (safer for concurrent usage)
- Works reliably across all deployment environments
- Follows Java best practices
Environment Details
- Affected: Production applications using Strata as a Maven dependency
- Deployment Patterns: Application servers (Tomcat, WebLogic, etc.), OSGi, multi-module applications
- Concurrency Patterns: ForkJoinPool, parallel streams, CompletableFuture
- Strata Version: All versions using current
ExtendedEnum implementation
Issue Summary
When using Strata as a dependency in applications with complex classloader hierarchies (application servers, OSGi containers, multi-module applications),
ExtendedEnuminitialization fails withClassNotFoundExceptionwhen first accessed fromForkJoinPoolworker threads or parallel streams.Stack Trace
Root Cause
Current Implementation (ExtendedEnum.java:174):
RenameHandler.lookupType()uses the thread context classloader (TCCL) as its primary mechanism to load classes. When this is unavailable or incorrect, it falls back to the classloader that loadedRenameHandleritself.Problem in Production Environments:
In complex classloader hierarchies (common in application servers, OSGi, etc.):
Common/Shared ClassLoader (parent)
RenameHandlerlives), Joda-Beans, GuavaApplication ClassLoader (child)
ExtendedEnumand provider classes likeStandardDayCountsForkJoinPool Worker Threads
nullor incorrectWhen
ExtendedEnum(child) callsRenameHandler.lookupType()(parent):null→ skippedRenameHandler's classloader is the parent → cannot seeStandardDayCountsin childClassNotFoundExceptionReproduction
The issue can be reproduced using a standalone Java program that simulates production classloader hierarchy (attaching it in a comment - unzip it at the project root):
cd standalone-reproducer ./run-production-sim.shBefore Fix:
Why Maven Tests Cannot be Used to Reproduce:
Maven's Surefire plugin uses a simple classloader hierarchy where all classes (Joda-Convert, Strata, test classes) are loaded by the same classloader. This allows
RenameHandler's fallback mechanisms to succeed. The issue only manifests in production environments with parent/child classloader separation.Proposed Solution
Replace
RenameHandler.INSTANCE.lookupType()withClass.forName()using an explicit classloader:Why This Works:
ExtendedEnum(the child/application classloader)Files to Change:
ExtendedEnum.parseProviders()- line 174CombinedExtendedEnum.parseChildren()- line 97Benefits:
Environment Details
ExtendedEnumimplementation