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-163 — StarConnector.
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:
- Replace the three
Enhancer blocks with Proxy.newProxyInstance, keeping each
interceptor's existing routing logic (including the $default$ fallback to the stub
Connector instance).
- Remove the
cglib:cglib:3.3.0 dependency from obp-api/pom.xml.
- Drop
--add-opens java.base/java.lang=ALL-UNNAMED from the runtime launch script,
the test argLine, and the README dev snippet.
- 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
Summary
On JDK 25 the API runs correctly, but only when the JVM is started with 6
--add-opensflags. 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
Proxyso that the--add-opens java.base/java.lang=ALL-UNNAMEDflag is no longer needed.Note: this is not a JDK 25 compatibility defect —
--add-opensis a supported JVMmechanism. 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-opensflags:Startup fails:
Root cause
CGLib generates a dynamic subclass of the
Connectortrait and loads it by reflectivelycalling the protected
ClassLoader.defineClass. Since JDK 9 the module system blocks thatcall unless
--add-opens java.base/java.lang=ALL-UNNAMEDis passed. This is the only flagtied to our own code.
CGLib usage (all three are
Enhancerproxies ofConnector)obp-api/src/main/scala/code/bankconnectors/package.scala:48-163—StarConnector.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— dynamicconnector, 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 inobp-api/pom.xml:164-168(lastupstream release ~2019). It is not pulled in transitively.
Proposed fix
Connectoris a Scalatrait(Connector.scala:122), i.e. an interface at the JVM level,so it can be proxied with
java.lang.reflect.Proxy.newProxyInstance+ anInvocationHandler.None of the three interceptors use any CGLib-specific feature (the
MethodProxyargument isignored; 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 theJDBC
Connectioninterface is wrapped with a JDK dynamic proxy. JDK dynamic proxies definetheir class through an internal privileged path and do not require
--add-opens.Steps:
Enhancerblocks withProxy.newProxyInstance, keeping eachinterceptor's existing routing logic (including the
$default$fallback to the stubConnectorinstance).cglib:cglib:3.3.0dependency fromobp-api/pom.xml.--add-opens java.base/java.lang=ALL-UNNAMEDfrom the runtime launch script,the test
argLine, and the README dev snippet../run_tests_parallel.sh) and start the server with the CGLib flagremoved to confirm boot + a live request through
StarConnector.Non-goals
The other five
--add-opensflags 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
net.sf.cglibimports remain.--add-opens java.base/java.lang=ALL-UNNAMED.Reference File
2026-07-01-17-15-jdk25-cglib-issue.md