Vauban rests on a simple principle: anything that can be computed at compile time is. No hot reflection, no dynamic proxies, no external bytecode library. This page documents the implementation choices that uphold that discipline.
Code generation via Class-File API and APT
CDI 4.1 describes a richly reflective object model: Bean<T>, BeanManager, InjectionPoint, AnnotatedType. Vauban refuses to evaluate that model at runtime. It compiles it.
Two tools run at process-classes:
-
vauban-indexer— walks the module path at compile time and writes an index file (META-INF/vauban/index.bin). The static equivalent of Weld’s runtimeBeanArchive. Zero dependency. -
vauban-processor— ajavax.annotation.processing.Processorthat consumes the index and emits bytecode through the Class-File API (JEP 484). No ASM, no Byte Buddy, no Gizmo.
For each bean the processor generates:
| Generated artefact | Role |
|---|---|
|
Implements |
|
Subclass generated for normal scopes. Intercepts every public method and delegates to the active context via |
|
For each |
|
For each |
|
Drift between |
APT pipeline — sequence diagram
The whole index, the whole injection grid, every proxy is written before javac finishes its job.
Runtime bootstrap sequence
The ApplicationContext is built in memory, with no reflection, no package opening.
Threading model
Vauban uses virtual threads (JEP 444) in two places:
-
Async observers — every
@ObservesAsyncis dispatched on anExecutors.newVirtualThreadPerTaskExecutor()internal to theEventDispatcher. No platform-thread pool, no bounded queue. -
Request context — when Vauban is embedded in Cassini or Foy, the
RequestContextis carried by aScopedValue(JEP 487) rather than aThreadLocal. That lets structured virtual threads propagate the context without leaks.
|
The |
AOT compatibility
No Class.forName, no Method.invoke, no Class.getDeclaredFields() runs hot. The runtime relies on:
-
direct invocations on the generated
_Factoryclasses; -
MethodHandlesfor@PostConstruct,@PreDestroy, observer and interceptor invocations; -
no dynamic class loading beyond standard
ServiceLoader.
Consequence: Vauban compiles to a GraalVM native image without a reflect-config.json. It works with Project Leyden CDS and with a minimal JLink image.
Exported JPMS modules
| Module | Main exports |
|---|---|
|
|
|
|
|
|
|
(internal, requires |
|
|
|
AES-256-GCM encrypted implementation of the SPI (optional) |
See the reference for the exhaustive list.
Sources
-
vauban-core — container runtime
-
vauban-processor — APT and Class-File API generators
-
vauban-indexer — build-time scanner
-
BUG.md — tracked reproducible bugs (VAU-PRX-002, VAU-INJ-001)