Skip to content

ExtendedEnum ClassNotFoundException in ForkJoinPool with Complex ClassLoader Hierarchies #2748

@oranoran

Description

@oranoran

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.):

  1. Common/Shared ClassLoader (parent)

    • Contains: Joda-Convert (where RenameHandler lives), Joda-Beans, Guava
    • Does NOT contain: Strata classes
  2. Application ClassLoader (child)

    • Contains: Strata JARs with ExtendedEnum and provider classes like StandardDayCounts
    • Can see parent classes via delegation
  3. 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:

  1. ExtendedEnum.parseProviders() - line 174
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions