Skip to content

feat(engine-java): classpath loading of AOT compiled modules (no runtime javac)#6400

Merged
delchev merged 4 commits into
masterfrom
feat/aot-classpath-modules
Jul 24, 2026
Merged

feat(engine-java): classpath loading of AOT compiled modules (no runtime javac)#6400
delchev merged 4 commits into
masterfrom
feat/aot-classpath-modules

Conversation

@delchev

@delchev delchev commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What

Engine-side support for AOT (ahead-of-time) module packaging: register a module's
already-compiled classes (shipped on the application classpath) through the same consumer /
bean-container path used for runtime-compiled registry sources - without invoking javac.

Changes

  1. JavaLoader.applyGeneration(nextGeneration, loader) - extracted (behavior-preserving) from
    rebuild(): the source-agnostic install (diff → notify JavaClassConsumers unload/load → swap
    client ClassLoader → rebuild client bean container → record).
  2. JavaLoader.installCompiledModules(List<LoadedClass>) - second producer. Compiled-module
    classes become the compiledGeneration; the installed generation is the union of
    registry-compiled + classpath-compiled sub-generations, so neither producer unloads the other.
  3. CompiledModuleClassProvider - on ApplicationReadyEvent, scans the classpath for
    META-INF/dirigible/<project>/.compiled markers (a UTF-8 list of the module's top-level class
    binary names), Class.forNames each via the application classloader, and installs them. Reads
    markers straight from the classpath, independent of the ClasspathExpander.

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

No JavaSynchronizer gate needed (by design)

A compiled module ships only .class (classpath) + META-INF/dirigible/<project> content
(models/UI/data) - no .java in its registry payload. JavaSynchronizer finds nothing to
compile for it (absence of .java is the gate), and the JavaLoader union makes the two producers
coexist.

Verification - engine-java 69/69

  • JavaLoaderTest: installCompiledModules registers a compiled class through the consumer path and
    it survives a registry rebuild that drops all authored sources.
  • CompiledModuleClassProviderTest: a .compiled marker on the classpath is discovered, the project
    derived from the path, and the listed class loaded via the app classloader.
  • CompiledModuleControllerRegistrationTest (end-to-end): a @Controller shipped by a compiled
    module is instantiated by the ComponentContainer, registered by the standard
    ControllerClassConsumer, and its GET route resolves through ControllerRouter - proving the
    outermost engine-reachable layer (a served route) with no runtime javac.

Follow-ups (separate)

  • Packaging (build/CI, not core): module build → jar = compiled classes + META-INF/dirigible/<project>
    content + the emitted .compiled marker (no .java) + POM with cross-model deps; image assembly
    from pinned versions.
  • Optional full-boot HTTP smoke (a fixture module jar in BOOT-INF/lib → HTTP 200) - largely
    redundant now that route resolution is proven at the component level above.

🤖 Generated with Claude Code

delchev and others added 3 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>
…AOT compiled modules

The producer that drives installCompiledModules: on ApplicationReadyEvent it scans the classpath for
META-INF/dirigible/<project>/.compiled markers (a UTF-8 list of the module's top-level class binary
names, # comments allowed), derives the project from the path, Class.forName's each through the
application classloader, and installs them - no runtime javac. Reads markers straight from the
classpath, so it does not depend on the ClasspathExpander laying the registry payload first; a later
registry rebuild unions the compiled classes back in.

Unit test + fixture (AotFixtureClass + a test .compiled marker): discover() finds the marker, derives
project "aot-test-mod", and loads the listed class via the app classloader. engine-java 68/68 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@delchev delchev changed the title feat(engine-java): install path for AOT compiled-module classes (step 1 - the engine seam) feat(engine-java): classpath loading of AOT compiled modules (no runtime javac) Jul 23, 2026
… (no javac)

Integration test closing the loop for AOT classpath loading: a @controller shipped by a compiled
module - installed via JavaLoader.installCompiledModules rather than compiled from registry source -
is instantiated by the ComponentContainer, registered by the standard ControllerClassConsumer, and
its GET route resolves through ControllerRouter. Proves the outermost engine-reachable layer (served
route) without any runtime javac. engine-java 69/69 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
}

@Override
public void publishEvent(Object event) {
@delchev
delchev merged commit 15efb85 into master Jul 24, 2026
10 checks passed
@delchev
delchev deleted the feat/aot-classpath-modules branch July 24, 2026 05:56
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.

2 participants