Skip to content

feat(engine-java): install path for AOT compiled-module classes (step 1 - the engine seam)#6400

Open
delchev wants to merge 2 commits into
masterfrom
feat/aot-classpath-modules
Open

feat(engine-java): install path for AOT compiled-module classes (step 1 - the engine seam)#6400
delchev wants to merge 2 commits into
masterfrom
feat/aot-classpath-modules

Conversation

@delchev

@delchev delchev commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What

Step 1 of AOT (ahead-of-time) module packaging: teach engine-java to register a module's
already-compiled classes (shipped on the application classpath) through the same consumer /
bean-container path it uses for runtime-compiled registry sources - without invoking javac.

This PR lands only the engine seam + unit tests; it does not yet wire a discovery producer or
change packaging (see Follow-ups). It's intentionally small so the core-loading change can be
reviewed on its own.

Changes

  1. JavaLoader.applyGeneration(nextGeneration, loader) - extracted (behavior-preserving) from
    rebuild(). It is the source-agnostic install: diff vs the current generation → notify every
    JavaClassConsumer (unload → load) → swap the client ClassLoader → rebuild the client bean
    container → record. rebuild() still owns compile + effective-bytecode + .class write +
    JavaCompiledEvent, and calls it.
  2. JavaLoader.installCompiledModules(List<LoadedClass>) - a second producer: compiled-module
    classes (already loaded by the application classloader) are recorded as the compiledGeneration
    and installed via applyGeneration. The installed generation is now the union of the
    registry-compiled and classpath-compiled sub-generations, so a registry rebuild never unloads a
    compiled module and vice-versa. An FQN clash between the two logs a warning (a mis-assembled
    image).

Every JavaClassConsumer (ControllerClassConsumer, listeners, websockets, handlers, scheduled,
EntityClassConsumer) and the ComponentContainer are unchanged - they already react to a
loaded Class, agnostic of how it was produced.

Why

Deployed apps today compile client .java from the registry at start (JavaSourceCompiler), which
is slow, all-or-nothing (one bad unit unregisters everything), and not immutable (no artifact
coordinate to scan for CVEs). The target is: ship modules as precompiled, versioned, signed jars on
the classpath and only run synchronizers on start. This PR is the minimal engine change that lets a
compiled module's controllers/DAOs/delegates register from the classpath.

Verification

engine-java 67/67 green, incl. a new JavaLoaderTest case: installCompiledModules registers a
compiled class through the consumer path, and it survives a registry rebuild that drops all authored
sources (absent from unloadedFqns).

Follow-ups (separate PRs)

  • Discovery producer - read META-INF/dirigible/<project>/ compiled-module markers (surfaced to
    the registry by the existing ClasspathExpander), Class.forName the listed FQNs on the app
    classloader, call installCompiledModules at startup.
  • compiled-mode gate in JavaSynchronizer - skip .java for compiled modules (no runtime
    javac).
  • Packaging - module build → jar (compiled classes + META-INF/dirigible/<project> content, no
    .java) + POM with cross-model deps.
  • End-to-end IT - a prebuilt module jar on the classpath registers its controller and serves,
    javac never invoked.

🤖 Generated with Claude Code

delchev and others added 2 commits July 23, 2026 19:35
…ild()

Behavior-preserving split so the generation-install path (diff vs current -> notify consumers
unload/load -> swap client loader -> rebuild bean container -> record) is source-agnostic: it takes a
pre-built FQN->LoadedClass map + the loader. rebuild() still owns compile + effective-bytecode +
class-file write + JavaCompiledEvent and calls applyGeneration() for the install.

Foundation for AOT-packaged `compiled` modules, where a second producer discovers already-compiled
classes on the classpath and drives the identical install path (no runtime javac). No functional
change; 66 engine-java tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… compiled-module classes

Second producer for the install path (AOT packaging): compiled-module classes already loaded by the
application classloader are recorded as the `compiledGeneration` and surfaced to the same
JavaClassConsumers via applyGeneration - NO runtime javac. The installed generation is now the union
of the registry-compiled (rebuild) and classpath-compiled sub-generations, so a registry rebuild does
not unload compiled modules and vice-versa. FQN clash between the two logs a warning (a mis-assembled
image; the "exactly one provider" assembly check catches it earlier).

Unit test: installCompiledModules registers a compiled class through the consumer path, and it
survives a registry rebuild that drops all authored sources (not reported in unloadedFqns). engine-
java 67/67 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant