New in the 0.4.0-SNAPSHOT development line — everything listed here landed after the 0.3.0 release and is not part of it. Every entry links to the section that documents it, and those sections carry the NEW badge. Each brick of the suite keeps its own page of this kind; the ecosystem index gathers them. When the next release train ships, this page is frozen with it and restarts empty on the dev line.
Running inside a Vauban layer
The Java SE launcher, and what changes once Vauban’s own class loader defines the application’s classes:
-
Vauban in Java SE — a page of its own for the SE container: which modules the launcher re-layers, why the entry point has to run inside the layer, the two resources the build ships, and the limits, from a bare boot layer to GraalVM
native-image, which is not supported.SeContainerInitializeritself works as the specification describes, on the module path, with no configuration. Vauban in Java SE. -
Java SE launcher —
java -p mods --add-modules ALL-MODULE-PATH -m io.vidocq.vauban.classloader/io.vidocq.vauban.classloader.Launch <module>/<main-class>runs the application inside a Vauban layer.--add-modules ALL-MODULE-PATHis required, since-mmakes the launcher the only root module.Launch.run(target, args)is the programmatic form and returnsfalse, doing nothing, when the thread already runs in a layer. It ships asio.vidocq.vauban:vauban-classloader, atruntimescope. The launcher, Maven artefacts. -
-Dvauban.launch.keep=<prefixes>— module-name prefixes the launcher keeps in the boot layer, on top of the built-in rules, for a test framework or an agent-attached library that must not move. A named target module is a root, so no keep prefix can hold the application itself back. Which modules are re-layered, System properties. -
In a Vauban layer, no load-time agent — when the loader defines the application’s classes, it weaves
ProxyLinkat definition andLoadTimeWeavingstands down: no dynamic agent, and none of the JDK warning that comes with it. It also weaves the produced types whose proxies it places, and the superclasses they need, so a placed proxy does not chain a third-party constructor. weaving tiers. -
The hello world shows the launcher form — "Build and run" gives the launcher command next to the plain
java --module-path … --module ….Mainis unchanged; what changes is that the application starts inside a layer. Getting started — build and run. -
The layer works from a
jlinkimage — inside a runtime image every module is served fromjrt:, and the launcher used to find nothing to re-layer there and refuse to start, with a message about--add-modulesthat named the wrong cause. It now re-layers those modules as it does a module path, so placed proxies, encrypted archives and load-time weaving work in ajlinkorjpackageimage too. Vauban in Java SE — limits.
Client proxies without opens
Client proxies for third-party produced types, without opens (vauban#42) — the jakartaee/cdi#1015 case, runnable as example-cdi1015-lib + example-cdi1015-app:
-
One strategy per case — Internals carries the full decision table for where a
_ClientProxyis generated and what it costs, resolved in order, first match winning, down to the shapes CDI 4.1 §3.10 makes unproxyable anywhere. client-proxy strategies. -
Placed in-package proxies under a Vauban loader — when a produced type has a package-private or protected overridable member, or no accessible constructor, its proxy must live in the type’s own package. The processor ships that proxy as bytecode in the bean archive and the loader defines it into the library’s package on a
findClassmiss: zeroopens, no agent, no rewritten jar, the library used as shipped, signature intact. The third-party constructors do not run for the proxy (vauban#24); when a superclass the loader does not define blocks that, a WARNING says so. The library must still export the package. client-proxy strategies, what the build ships. -
Fully-public eligibility walks the whole superclass hierarchy — up to but excluding
java.lang.Object. A method inherited from a superclass is neither declared by the produced type nor overridden by the proxy, so judging declared methods alone handed out a proxy that silently ran the superclass body against its own empty state. client-proxy strategies. -
Which proxy do I get, and what do I have to do? — a table in Usage maps each situation to what Vauban does and what is asked of you, from "nothing" for an ordinary bean to "start through the launcher" for a produced class whose proxy must be placed. A "What failure looks like" subsection quotes the three diagnostics verbatim. Which proxy do I get.
-
Boot-time opens are opt-in —
-Dvauban.opens.auto=true(vauban#42, Stage 3b) opens a listed produced type’s package toio.vidocq.vauban.coreat boot, through the weaving agent, and only when the package is not already open. It is off by default: opening another module’s package behind the user’s back is what this project faults runtime CDI implementations for needing. Left off, the container refuses and logs every way to avoid it. client-proxy strategies, System properties.
Build tooling and switches
-
vauban:modularize— bound toprepare-package, opt-in: it gives the non-modular dependency jars of the closure amodule-infoof their own, written intotarget/vauban-modularized/under the original file name.requirescomes from the types each jar uses, every package is exported,META-INF/servicesbecomesprovides, and theServiceLoaderlookups found in the bytecode becomeuses. It fails on a split package between two automatic jars and leaves automatic any jar whose own lookup cannot be declared legally, with the reason inreport.txt. The copies never leavetarget/. It used to bevidocq:modularize.vauban:modularizein detail. -
vauban:enhance-dependencies, and a copy that declares itself — bound topackage, opt-in and a last resort: an enhanced copy of a dependency jar carrying the co-located proxy, for when the shipped in-package proxy cannot work — no Vauban layer at runtime, a library that exports nothing, or a native image. The copy declares what it is: it drops the inherited signature files and per-entry digests rather than carrying a signature over content that no longer matches, and recordsVauban-Enhanced-From,Vauban-Enhanced-DigestandVauban-Enhanced-By. Those copies are not the artefact their coordinates name, and declaring them to SBOM and attestation tooling is the governance cost that keeps the goal opt-in. Maven plugin goals, client-proxy strategies. -
vauban:generatepre-generates every intercepted subclass the container needs — it used to write<Bean>$$Interceptedonly for a bean with a class-level binding. It now writes one for every bean the container would wrap: a method-level binding (declared, inherited, or on an interface default method), a constructor binding, or an@AroundInvokemethod of the bean’s own. It lists them in its_VaubanComponents, which it did not, and writes none for an@Interceptorclass. A module the plugin built, with such a bean, used to fail on the strict module path without anopenswithCould not define interceptor subclass; it now boots (vauban#121). Maven plugin goals. -
Reproducible generated output — the processor and
vauban:generatewrite the same files for the same input from one build to the next: provider entries and generated methods come out in a stable order, where the order used to depend on hash iteration and on what the JVM had loaded before. Code generation. -
-Avauban.producerProxy=error|warn|note— the severity of the diagnostic emitted when a normal-scoped@Producesreturns a class type that is not build-time proxyable across a module boundary, which leaves the runtime to fall back to a reflective proxy and anopens. Defaultnote;errorenforces zero fallback. Annotation-processor options. -
-Dvauban.annotations.reflection=allow|warn|forbid— what the container may do when an annotation reaches it as an object rather than as index data. Matching and injection never reflect; four other things can, and this switch governs all four. Setforbidin a test to prove a deployment needs no reflection, orwarnto see what a native image would need metadata for. A module compiled with the Vauban APT needs none of them, and it also carries a literal for each public qualifier or interceptor binding it uses from a dependency built without the processor, soforbidholds for those too. System properties, AOT compatibility.
Interception of inherited and default methods
Interceptors now reach the methods a bean inherits, interface default methods included, and a bean that cannot be intercepted is told why. Several of these change what an existing application does:
-
Bindings on interface default methods apply — an interceptor binding on an interface default method the bean does not override now selects its interceptor, by the rule that already held for a superclass method: the binding stops applying once a class of the bean overrides the method. The class-level bindings of an interface still never apply. A bean whose only binding sits on such a method is now intercepted, so it must be proxyable: a
finalbean class, or a bean with afinalmethod, bound only through a default method used to deploy un-intercepted and now fails with aDeploymentException. Which methods the intercepted subclass covers, unproxyable intercepted beans. -
Inherited
protectedand package-private methods are intercepted at run time — the$$Interceptedsubclass the container generates when none was pre-generated, and the onevauban:generatewrites, now override the protected and package-private methods the bean inherits, as the processor’s subclass already did. A call the bean makes on itself to such a method now runs its interceptors too. Which methods the intercepted subclass covers. -
Client proxies forward default methods, and reach a shadowed one through its interface — a normal-scoped bean’s client proxy, the processor’s and the run-time one, now forwards the interface default methods the bean does not override to the contextual instance, where the default body used to run on the proxy instance, with no interceptor. A default method that a superclass method the bean does not inherit (a private one, say) hides from the JVM is now called through its interface by the generated subclass and proxies, when that interface can be named, where the call used to fail with
IllegalAccessErrororAbstractMethodError. Which methods the intercepted subclass covers. -
getMethod()andgetInterceptorBindings()see the inherited declaration — for a method the bean inherits,InvocationContext.getMethod()now returns the superclass method or the interface default method the call runs instead of the generated$$super$bridge, andgetInterceptorBindings()now includes the method-level bindings of that declaration. An interceptor that reads either one sees more than before for inherited and default methods. Which methods the intercepted subclass covers. -
An unproxyable intercepted bean is a deployment problem, with its reason — the check the container makes when it wraps an intercepted bean (one the deployment validation has not already rejected) now throws a
DeploymentExceptionfor afinalclass too, where it threw aDefinitionException, and each of its three messages (final class, final method, only a private no-arg constructor) ends with why the bean is intercepted: the binding, and the declaration that carries it. Code or tests that expected the former exception type or texts need updating. unproxyable intercepted beans. -
What Java source cannot override is emitted as bytecode — a bean that inherits a member whose signature names a type its package cannot name (a package-private type of another package, a private nested type) used to break the build: the processor wrote an override Java source cannot express. It now emits that intercepted subclass or client proxy, a producer’s included, as bytecode, which overrides and forwards by descriptor, and the module’s source
_VaubanComponentsreaches it through its own lookup, with noopens(vauban#120, BUG-20261004-09). A method whose return type the subclass may not access is left out of the subclass and runs un-intercepted, where the run-time subclass used to throwIllegalAccessErroron every call. Which methods the intercepted subclass covers. -
New processor warnings — when a shadowed default method cannot be reached through a nameable interface, the processor’s client proxy and intercepted subclass leave it out, and when a method’s return type cannot be accessed, the intercepted subclass leaves it out. The processor says so in a compiler warning:
[Vauban] <bean>: the client proxy does not forward the inherited default method …(the producer’s client proxy, on the producer method, for a producer), or[Vauban] <bean>: the generated subclass does not intercept …, each with the reason and what a call does instead. Which methods the intercepted subclass covers. -
Every processor client proxy forwards inherited methods — a normal-scoped bean with private constructors only, or a top-level class whose own name contains
$, got a proxy built from the index that forwarded only the methods the bean class declares, so a superclass method or a default method ran on the proxy instance. Such a bean now gets a proxy that forwards them like any other. A type whose own name contains$is also named correctly in the intercepted subclass, the producers' proxies and the annotation artefacts, which did not compile, and a normal-scoped producer of a nested class or interface now gets a build-time proxy (vauban#122). Which methods the intercepted subclass covers. -
Intercepted beans with an
@Injectconstructor underforbid— such a bean read its constructor parameters' qualifiers by reflection and could not be created under-Dvauban.annotations.reflection=forbid. It now takes them from its descriptor (BUG-20261007-03). Which methods the intercepted subclass covers. -
Lifecycle callbacks are not business methods — an
@AroundInvokeinterceptor no longer runs around a bean’s@PostConstructand@PreDestroycallbacks (VAU-INT-006, vauban#114). Those callbacks reach only the interceptors' own lifecycle methods, whereInvocationContext.getMethod()isnull, as Jakarta Interceptors 2.2 requires. With a class-level binding such as@Transactional,@Retryor@Timed, the interceptor used to wrapinit()anddispose()as if they were business calls: a transaction around the callback, a metric or a log line named after it. Neither happens any more. The processor’s intercepted subclass and the one the container generates use the same list of methods to leave out:@Injectinitializers, the target class’s interceptor methods, and the lifecycle callbacks. Which methods the intercepted subclass covers. -
Recompile with the matching processor — the classes the processor generated live in your archive, so an archive compiled by an earlier processor keeps them until it is recompiled: its client proxies forward no default method, and a bean bound only through an interface default method has no pre-generated
$$Interceptedsubclass. The container now intercepts that bean and generates the subclass itself. On the class path that works; on the module path, with the bean’s package not opened toio.vidocq.vauban.core, the deployment fails withDeploymentException: Could not define interceptor subclass, where the earlier container left the bean un-intercepted. Recompiling with this release’s processor fixes it, and also fixes a bean bound only through a superclass method, which failed the same way before. Which methods the intercepted subclass covers.
Web contexts
-
@SessionScopedwithvauban-webcontexts— a new module provides the session context CDI Lite leaves to the servlet container: one instance per session, kept in the session’s attributes, destroyed with the session between@BeforeDestroyedand@Destroyed(SessionScoped.class). Foy drives it throughfoy-cdi-vauban; another web container drives it throughWebContexts. It used to fail withContextNotActiveException: No context registered for scope jakarta.enterprise.context.SessionScoped(Vidocq/foy#21). Web contexts. -
RequestContextControllerand@ActivateRequestContext— the two CDI Lite ways to activate the request context outside a request were missing: the built-in@Dependentcontroller and the built-in interceptor (priorityPLATFORM_BEFORE + 100). Foy activates the request context through the controller, so under Vauban a@RequestScopedbean used from a servlet failed withContextNotActiveException: RequestScope is not active; it now gets one instance per request (vauban#147). Activating the request context. -
The request context is per thread, with its events — activated outside Cassini’s scope, the request context kept its state in a field every thread shared, so two concurrent activations overwrote each other’s instances; each thread now has its own. Activating and deactivating it fires
@Initialized,@BeforeDestroyedand@Destroyed(RequestScoped.class), which Vauban never fired, Cassini’s requests included (vauban#147). Activating the request context.
Extensions
-
Module lookups for trusted extensions —
ModuleLookups.lookupFor(Class<?>)gives an extension a full-privilege lookup on a class the container manages. The lookup is supplied by the generated_VaubanComponentsof the class’s package, whether the processor or the Maven plugin wrote it, so aprivatemember such as a MicroProfile Fault TolerancefallbackMethodis reachable with noopens. The package is exported only to the extension modules named inio.vidocq.vauban.core, and a provider hands its lookup only to the container. Module lookups for trusted extensions. -
An extension can tell build time from container start —
ExtensionPhase.isBuildTime()istruewhile the annotation processor or the Maven plugin runs a build compatible extension andfalsewhen the container does, so an extension that checks the deployment’s environment can leave that check to the container start (ravel#21). Build time or container start. -
Synthetic beans stay out of the bean list — an application whose extension synthesised a bean in the processor failed to boot with a
NullPointerException: the bean list named the synthetic bean’s type as if it were a managed class (ravel#21). Build Compatible Extensions. -
A synthetic bean’s language-model types survive build time — a type an extension gives the processor as a language-model type,
addBean(…).type(types.ofClass(info))for a class the compilation is still producing, was validated at build time but dropped from the metadata the runtime reads: the bean was typedObjectalone at run time and its injection points were unsatisfied. Class types are now written by name and loaded at run time; parameterized language-model types still are not (vauban#127, BUG-20261008-01). Build Compatible Extensions. -
Every synthetic param survives build time — when the processor runs an extension, the params it gives a synthetic bean or observer reach the creator: arrays,
ClassInfo,Annotation,AnnotationInfoandInvokerInfoused to be dropped without a word,ClassandEnumparams were lost at boot, and a synthetic observer got no param at all, so the creator readnull. A param that cannot be recorded now fails the build, and one naming a class the boot cannot load fails the deployment (vauban#130, BUG-20261008-02). Build Compatible Extensions. -
@Registrationsees what every@Enhancementdid — each phase now runs for every extension before the next phase starts, and@Registrationgets the beans as@Enhancementleft them, the classes it made beans included. An extension run by the processor used to miss a class it had made a bean itself (Foy’s unscoped@WebServlet), and an extension’s@Registrationcould run before another extension’s@Enhancement.BeanInfo.scope().name()also no longer throws for a built-in scope at build time (vauban#131, BUG-20261008-03). Build Compatible Extensions. -
Stereotypes and custom scopes are bean-defining at build time — the processor only looked at classes carrying one of a fixed list of annotations (
@ApplicationScoped,@RequestScoped,@Dependent,@Singleton,@Produces,@Interceptor) and the extensions' triggers. A class whose only bean-defining annotation was an application stereotype, a custom scope,@SessionScopedor@Modelwas no bean. Any annotation meta-annotated@Stereotype,@NormalScopeor@Scopenow counts, whether it is declared in the same compilation or in a library (vauban#132, BUG-20261008-04). Bean. -
Annotations an
@Enhancementadds keep their members —ClassConfig.addAnnotation(NamedLiteral.of("x"))named the bean with its default name, and a qualifier added with members, to a class or a field, lost them, so an injection point that told two beans apart by a member was unsatisfied. The build-time patch now records members, the container applies them, and a nested class’s default name no longer keeps itsOuter$prefix on that path. A patch written by an earlier build still reads, without members (vauban#135, BUG-20261008-05). Build Compatible Extensions. -
Which generator covers what — a generated provider declares what it runs in-module (
VaubanComponentProvider.coverage()), andVaubanContainer.codegenCoverage()tells, per bean, observer and interceptor, which generator covers each operation and which falls back to reflection. The Vidocq dev console shows it. A provider generated before this answersnull. Code generation.
Fixes that change behaviour
-
Bean methods that declare checked exceptions compile — an observer such as
void onStart(@Observes @Initialized(ApplicationScoped.class) Object event) throws Exception, a producer, an initializer or a constructor that declares a checked exception made the generated_VaubanComponentsfail to compile withunreported exception java.lang.Exception. The generated code now rethrows it, and the exception reaches the container unwrapped (vauban#145). Code generation. -
Intercepted beans in a package you do not export — a bean with an interceptor (a
@Retrymethod, for instance) in a package the module neither exports nor opens failed on first use withIllegalAccessException: … does not export … to module io.vidocq.vauban.core: the subclass was created in-module by the generated provider, but its wiring method was then called by reflection. That call now goes through the module lookup the provider grants, so nothing has to be exported (BUG-20261010-02). Module lookups. -
A producer’s disposer runs once —
Bean#destroyran it twice (vauban#115). Producers and@Disposes. -
A disposer’s other parameters are injection points — qualifiers included: a qualified parameter used to fail validation as unsatisfied, or receive the
@Defaultbean (vauban#89). Producers and@Disposes. -
Default
@Namedname of a nested bean class —Outer.ReportServiceis now namedreportService, no longerouter$ReportService. A lookup by the old name must be updated (vauban#116). Qualifiers. -
Nested beans work on the module path — a normal-scoped
staticnested bean now gets a source client proxy that its module’s provider instantiates, where it used to be left out of the provider and fall back to reflection, which a module that opens nothing refuses. Which proxy do I get. -
The generated provider uses the
@Injectconstructor — when a bean has both an@Injectconstructor and a no-arg one, the provider used to build it through the no-arg one only, so the container fell back to reflection and a named module withoutopenscould not create the bean (grimm#15). Code generation. -
Failures are reported, not swallowed — a listed bean class that cannot be loaded is named in a warning instead of vanishing, a failed field injection reaches the caller instead of leaving the field
null, and the build compatible extension phase resolves bean classes across every loader of the deployment, so CDI invokers no longer fall back to reflection undervidocq:dev(grimm#15, vauban#98). Failures are reported, not swallowed. -
beans.xmldiscovery modes are honoured — default SE discovery used to read everyMETA-INF/beans.xmlasall, so anannotated(or empty)beans.xmlturned every concrete class of the archive into a bean, and anoneone did not skip it.annotated,allandnonenow behave as the specification describes. Default discovery: bean archives.
The upgrade-relevant changes are gathered in Migration — upgrading from 0.3.0.