Skip to content

feat(runtime): drop-in /modules classpath directory in the container image (#6592) - #6594

Merged
delchev merged 1 commit into
masterfrom
feat/modules-drop-in-classpath
Aug 7, 2026
Merged

feat(runtime): drop-in /modules classpath directory in the container image (#6592)#6594
delchev merged 1 commit into
masterfrom
feat/modules-drop-in-classpath

Conversation

@delchev

@delchev delchev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Closes #6592.

#6400 added classpath loading of AOT compiled modules (CompiledModuleClassProviderJavaLoader.installCompiledModules, no runtime javac), but there was no supported way to get such a module jar onto the classpath of the shipped image: the entrypoint used Spring Boot's JarLauncher (java -jar), which has no mechanism for external jars. Verifying #6400 required exploding the fat jar and copying the module into BOOT-INF/lib, i.e. mutating the published artifact.

What changes

  • build/application/Dockerfile — launch through PropertiesLauncher with -Dloader.path=/modules, and ship an empty /modules directory. A downstream image COPYs AOT module jars there, or they are volume-mounted at run time; the platform jar is consumed verbatim. --add-opens flags unchanged. LOADER_PATH (comma-separated, honored natively by the launcher) is the override for other locations — no new Dirigible configuration property.
  • ClassPathIndex — appends the same loader.path / LOADER_PATH jars to the javac classpath, so registry .java sources can still be compiled against a drop-in module's classes. (They could be, back when the module jar was hand-copied into BOOT-INF/lib; without this the drop-in route would silently lose that.) A directory contributes its *.jar files in a stable order, a jar contributes itself, missing segments are skipped.
  • Docs — a section in components/engine/engine-java/README.md and in the module's guide (CLAUDE.md) covering what goes into /modules, the LOADER_PATH override and the relationship to feat(engine-java): classpath loading of AOT compiled modules (no runtime javac) #6400, plus a comment in the Dockerfile.

The derived OpenTelemetry images (open-telemetry/dirigible/*/Dockerfile) inherit the entrypoint and only replace /dirigible.jar at the same path, so they need no change.

Verification

Built the image locally and ran it both ways. Module jar under test: a gen.aotdemo.AotDemoController class, its META-INF/dirigible/aot-demo/.compiled marker and a registry payload file.

Empty /modules is a strict no-op — compared against a java -jar (JarLauncher) baseline of the same jar:

JarLauncher baseline PropertiesLauncher, empty /modules
Startup Started DirigibleApplication in 18.27 seconds 19.20 seconds (in-container; 20.7s in the first local run)
Compile classpath 1051 entries 1051 entries
AOT registration line none none
/actuator/health/readiness 200 200

A normalised diff of the two boot logs (timestamps/thread ids/numbers stripped) shows only concurrent-init ordering noise — no structural difference. PropertiesLauncher reads Start-Class from the jar manifest, so the application boots exactly as with -jar.

Module jar in /modules (-v .../modules:/modules:ro):

ClassPathIndex          - Materialised compile-time classpath: [1052] entries        # 1051 + the drop-in jar
ControllerRouter        - Registered controller [aot-demo::gen.aotdemo.AotDemoController] (1 routes)
CompiledModuleClassProvider - Registered [1] class(es) from AOT compiled module(s) on the classpath

$ ls /target/dirigible/repository/root/registry/public/aot-demo
.compiled  marker.json                                                              # payload expanded

$ curl -u admin:admin .../services/java/aot-demo/gen/aotdemo/AotDemoController/ping
pong from the aot-demo drop-in module                                               # 200

No javac activity for the module — the only log mention is the controller registration.

Classpath order (requirement 2 of the issue): confirmed against the shipped PropertiesLauncher bytecode — getClassPathUrls() adds the loader.path paths first, then getClassPathUrlsForRoot() (BOOT-INF/classes + BOOT-INF/lib). So a drop-in jar can shadow a platform class in principle, but not by accident: module packages are gen.* / custom.*, which the platform does not use. Stated explicitly in the docs.

Tests: ClassPathIndexTest covers the loader.path resolution (directory → its jars in stable order, jar → itself, blank/missing/empty → nothing). engine-java 72/72 green; formatter and -P release javadoc clean on the module.

🤖 Generated with Claude Code

…image (#6592)

#6400 added classpath loading of AOT compiled modules, but there was no supported way
to get such a module jar onto the classpath of the shipped image: the entrypoint used
Spring Boot's JarLauncher (java -jar), so verifying #6400 meant exploding the fat jar
and copying the module into BOOT-INF/lib - mutating the published artifact.

The image now launches through PropertiesLauncher with -Dloader.path=/modules and ships
an empty /modules directory. A downstream image COPYs module jars there, or they are
volume-mounted at run time; the platform jar is consumed verbatim. LOADER_PATH overrides
the location - no Dirigible configuration property is involved.

ClassPathIndex appends the same loader.path / LOADER_PATH jars to the javac classpath, so
registry sources can still be compiled against a drop-in module's classes (they could be,
back when the module jar was hand-copied into BOOT-INF/lib).

Verified with the built image (module jar = a gen.aotdemo controller + its .compiled marker
+ registry payload):

- empty /modules is a no-op: boot log, 1051-entry compile classpath and startup time
  (19.2s vs 18.3s) match the JarLauncher baseline; a normalised diff of both boot logs
  shows only concurrent-init ordering noise
- /modules with the module jar: payload expanded into registry/public/aot-demo, compile
  classpath 1052 entries, "Registered [1] class(es) from AOT compiled module(s) on the
  classpath", GET /services/java/aot-demo/gen/aotdemo/AotDemoController/ping -> 200, and
  no javac activity for the module
- classpath order confirmed against the shipped PropertiesLauncher bytecode: loader.path
  entries precede BOOT-INF/classes + BOOT-INF/lib. Shadowing a platform class is therefore
  possible in principle but not by accident - module packages are gen.* / custom.*, which
  the platform does not use.

Documented in the engine-java README + guide and in the Dockerfile itself.
engine-java 72/72 unit tests green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@delchev
delchev force-pushed the feat/modules-drop-in-classpath branch from 2a15dc7 to 21e80ea Compare August 7, 2026 08:51
@delchev
delchev merged commit 3d0c5e2 into master Aug 7, 2026
10 checks passed
@delchev
delchev deleted the feat/modules-drop-in-classpath branch August 7, 2026 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AOT compiled modules: drop-in /modules classpath directory in the runtime image (PropertiesLauncher loader.path) — follow-up to #6400

1 participant