Skip to content

Replace CGLib connector proxies with JDK dynamic Proxy to drop the java.base/java.lang --add-opens requirement #34

Description

@hongwei1

Summary

On JDK 25 the API runs correctly, but only when the JVM is started with 6 --add-opens
flags. Removing them crashes the server at startup. The crash comes from CGLib, which is
an unmaintained library used in only three places, all of them our own code. This issue
tracks replacing CGLib with the JDK's built-in dynamic Proxy so that the
--add-opens java.base/java.lang=ALL-UNNAMED flag is no longer needed.

Note: this is not a JDK 25 compatibility defect — --add-opens is a supported JVM
mechanism. This is a cleanup: remove a dead dependency and the one flag that actually
crashes.

Reproduction

Build the jar and run it with no --add-opens flags:

java -jar obp-api/target/obp-api.jar

Startup fails:

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException
  --> Unable to make protected final java.lang.Class
      java.lang.ClassLoader.defineClass(...) accessible:
      module java.base does not "opens java.lang" to unnamed module

Root cause

CGLib generates a dynamic subclass of the Connector trait and loads it by reflectively
calling the protected ClassLoader.defineClass. Since JDK 9 the module system blocks that
call unless --add-opens java.base/java.lang=ALL-UNNAMED is passed. This is the only flag
tied to our own code.

CGLib usage (all three are Enhancer proxies of Connector)

  • obp-api/src/main/scala/code/bankconnectors/package.scala:48-163StarConnector.
    Not lazy, so it is created during boot — this is the startup crash site. It is also the
    default connector (connector=star) and sits on the request path.
  • obp-api/src/main/scala/code/bankconnectors/InternalConnector.scala:29-34 — dynamic
    connector, created lazily.
  • obp-api/src/main/scala/code/bankconnectors/ConnectorUtils.scala:22-42 — test-only
    (connector=proxy), created lazily.

Dependency: cglib:cglib:3.3.0, declared directly in obp-api/pom.xml:164-168 (last
upstream release ~2019). It is not pulled in transitively.

Proposed fix

Connector is a Scala trait (Connector.scala:122), i.e. an interface at the JVM level,
so it can be proxied with java.lang.reflect.Proxy.newProxyInstance + an InvocationHandler.
None of the three interceptors use any CGLib-specific feature (the MethodProxy argument is
ignored; they dispatch with method.invoke(...)), so the translation is mechanical.

We already use exactly this pattern in
obp-api/src/main/scala/code/api/util/http4s/RequestScopeConnection.scala:125-150, where the
JDBC Connection interface is wrapped with a JDK dynamic proxy. JDK dynamic proxies define
their class through an internal privileged path and do not require --add-opens.

Steps:

  1. Replace the three Enhancer blocks with Proxy.newProxyInstance, keeping each
    interceptor's existing routing logic (including the $default$ fallback to the stub
    Connector instance).
  2. Remove the cglib:cglib:3.3.0 dependency from obp-api/pom.xml.
  3. Drop --add-opens java.base/java.lang=ALL-UNNAMED from the runtime launch script,
    the test argLine, and the README dev snippet.
  4. Run the full suite (./run_tests_parallel.sh) and start the server with the CGLib flag
    removed to confirm boot + a live request through StarConnector.

Non-goals

The other five --add-opens flags come from third-party libraries (Javassist, Kryo/Chill,
Pekko, GraalVM, Jackson, and the Scala reflection runtime), not from our code. They are the
normal cost of those libraries on a modern JDK and are out of scope here.

Acceptance criteria

  • CGLib removed from the build; no net.sf.cglib imports remain.
  • Server boots and serves requests on JDK 25 without
    --add-opens java.base/java.lang=ALL-UNNAMED.
  • Full test suite green.

Reference File

2026-07-01-17-15-jdk25-cglib-issue.md

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions