Vauban is a CDI SE container: SeContainerInitializer works exactly as the specification
describes, on the module path, with no configuration. This page covers what is specific to
Vauban — the launcher, and why it matters the moment your beans produce types from modules
that know nothing about CDI.
The standard way
try (SeContainer container = SeContainerInitializer.newInstance()
.addBeanClasses(CheckoutService.class, Integrations.class)
.initialize()) {
container.select(CheckoutService.class).get().checkout("order-7");
}
java -p mods -m app/app.Main
Everything sits on the module path, in the boot layer, loaded by the platform’s application
class loader. For beans you compile, and for producers of fully-public third-party types, nothing
more is needed: proxies are generated at build time and resolve with zero opens
(client-proxy strategies).
Default discovery: bean archives NEW
With neither addBeanClasses nor addPackages, initialize() discovers beans in every archive
(jar or directory) of the class loader that contains a META-INF/beans.xml. Its
bean-discovery-mode is honoured:
| Mode | Beans discovered in the archive |
|---|---|
|
Classes with a bean-defining annotation (a scope, a stereotype, |
|
Every concrete class. |
|
None; the archive is skipped. |
A beans.xml that cannot be read, or that names another mode, fails the start with a
DeploymentException. Before 0.4.0-SNAPSHOT, Vauban read every beans.xml as all.
When the boot layer is not enough
A normal-scoped @Produces returning a third-party class that has a package-private or
protected overridable member — declared or inherited — or no accessible constructor needs a
client proxy that lives inside the type’s own package: a proxy anywhere else can neither override
nor forward that member, and cannot chain that constructor. On the boot layer, defining a class into
another module’s package requires that module to opens it, so the container refuses by default and
tells you exactly what is missing:
Vauban needs [it.liba] of it.liba opened to io.vidocq.vauban.core for a runtime producer
proxy, and will not do it on its own. Pick one: start the application through
io.vidocq.vauban.classloader.Launch (in a Vauban layer the loader defines the shipped proxy
inside the package itself — zero opens, no agent); …
The first remedy is the launcher.
The launcher
java -p mods --add-modules ALL-MODULE-PATH \
-m io.vidocq.vauban.classloader/io.vidocq.vauban.classloader.Launch app/app.Main [args]
--add-modules ALL-MODULE-PATH is required. With -m, the launcher’s module is the only root
module, so without it neither your application nor the container would be resolved in the boot
layer; the launcher stops and says so.
Launch loads the application modules of the boot layer again in a child layer under a single
self-first VaubanClassLoader, then invokes your public static void main(String[]) from inside
that layer. Your code does not change; SeContainerInitializer works as before. What changes is
who defines your classes — and once that is Vauban’s own loader:
- Placed proxies
-
A client proxy that must live in a third-party produced type’s package is shipped by the build as a resource of your bean archive (
META-INF/vauban/placed/…_ClientProxy.class, next to the placement manifestMETA-INF/vauban/required-opens.list), and the loader defines it into that package. A loader may define classes in a package it owns;opensgoverns reflective access across modules and has nothing to say here. Zeroopens, no agent, no rewritten jar — the library is used exactly as its author shipped it, signature intact. The library must stillexportthat package: the container instantiates the placed proxy fromio.vidocq.vauban.core. - No double construction
-
The loader weaves the side-effect-free
(ProxyLink)entry constructor into the produced type as it is defined, and into the superclasses it defines that have no usable no-arg constructor, then retargets the placed proxy onto it: the third-party constructors do not run for the proxy — the vauban#24 guarantee, extended to types you do not compile. If a superclass the loader does not define blocks that chain, a WARNING names the type, and its proxy runs the business constructor on a throwaway instance. - No load-time agent
-
An unwoven normal-scoped bean (classes written after javac by an IDE) is woven at definition by the loader’s cdi-proxifier. The dynamic agent of the load-time tier is not used in a Vauban layer.
Which modules are re-layered
Every module of the boot layer with a file: location, except those that must stay in the boot
layer — the re-layered modules then read them from there:
-
the platform (
java.,jdk.), Jakarta (jakarta.) and the Vauban container modules (io.vidocq.vauban.api,.core,.indexer,.weaver,.classloader,.classloader.spi,.sjar,.processor,.junit) — application code of your own underio.vidocq.vauban.is re-layered like any other; -
automatic modules: re-layered, an automatic module would read its own boot-layer twin, and resolution would fail;
-
modules with a
javax.,sun.orcom.sun.package, which the Vauban loader refuses to define — they would be empty shells in the layer; -
modules whose name starts with a prefix listed in
-Dvauban.launch.keep=org.junit,com.acme.infra; -
and every module a kept module reads, transitively: a kept module cannot read a re-layered one.
The module you name in <module>/<main-class> is the application: no prefix keeps it, so what it
reads is not held back through it either. It must still end up re-layered. If it is automatic, owns a
javax., sun. or com.sun. package, or is read by a kept module, the launcher refuses to run it
rather than run it exactly as without the launcher.
With the bare <main-class> form there is no module name to treat as a root, so a keep prefix can
hold the application back; name the module.
The policy is public as VaubanLayerFactory.applicationPaths(configuration, keptPrefixes, roots).
Why the entry point must run in the layer
It is tempting to want initialize() to do this transparently. It cannot: your Main left in
the boot layer holds the boot layer’s Gadget.class, while a placed proxy extends the layer’s
Gadget — container.select(Gadget.class) would end in ClassCastException. The caller’s
classes are its identity, so the launcher re-enters the application through main, once, from
inside the layer.
Launch.run(target, args) returns false, without doing anything, when the calling thread already
runs in a Vauban layer. An application can therefore make it the first statement of its own main:
public static void main(String[] args) throws Throwable {
if (Launch.run("app/app.Main", args)) return; // re-launched inside the layer: done here
// … the application, now running inside the layer
}
The Vidocq runtime applies the same policy through Vidocq.run / @VidocqMain, with its own bricks
kept as well and the caller’s module as the root. CDI-agnostic libraries are re-layered there too, so
their proxies are placed.
What the build ships
The annotation processor writes, into your bean archive:
| Resource | Role |
|---|---|
|
The placement manifest: one produced type per line that cannot be proxied from the producer’s
package. The loader places the proxies it has bytes for; on a bare boot layer, the opt-in
|
|
The co-located proxy bytes, emitted with the Class-File API for the shapes a co-located proxy can overcome. Static generation, nothing generated at runtime — the loader only places the class. |
A final or sealed class, a final method or an abstract class are unproxyable anywhere (CDI
4.1 §3.10): nothing is shipped for them, and they remain deployment errors. Nothing is shipped
either for a produced type that is not public, or for a non-static nested one — a co-located proxy
could serve those two, and does not today.
Limits
- Bare boot layer
-
Without the launcher, the in-package cases fall back to the documented levers: make the type fully public,
vauban:enhance-dependenciesas a last resort, a hand-writtenopens, or the opt-in-Dvauban.opens.auto=true. The container says so at boot; it never degrades silently. The build says so too: the proxy and the list of types needing one are shipped either way, so the Maven plugin reports which types will need a Vauban layer to be placed. - Kept modules
-
Placement needs the library to be defined by the Vauban loader. A produced type in a module kept in the boot layer (see above) falls back to the bare-boot-layer levers. A kept module also keeps reading the boot-layer copies of what it requires, so do not keep a module that hands application types to or from re-layered code.
- An exported package
-
Placement replaces the
opens, never theexports. The container instantiates the placed proxy fromio.vidocq.vauban.core, so the produced type’s package must be exported to it. A library that exports nothing falls back to the bare-boot-layer levers, and the boot-time warning says so rather than failing later at the injection point. - What the placed proxy forwards
-
The produced type’s own non-private members, every inherited public member, the members inherited from superclasses in the same package, and the protected members inherited from superclasses in other packages (through a
MethodHandle). A package-private member inherited from a superclass in another package is not forwarded: no class outside that package can override it, wherever the proxy lives. - Entry point
-
Launchinvokespublic static void main(String[]); the instance and non-publicmainforms of JDK 25 are not supported. jlinkandjpackage-
Supported. Inside a runtime image every module is served from
jrt:rather than from a jar on a module path; the launcher re-layers it from there, and placed proxies are defined exactly as they are on a module path.jpackagepackages such an image, so the same holds. - GraalVM
native-image -
A placed class is defined at runtime, which
native-imagedoes not support.vauban:enhance-dependencies— the proxy rewritten into a copy of the dependency at build time — remains the AOT path. CDS and Leyden are unaffected: the bytes are produced at build time.
See also
-
Internals — client-proxy strategies — the full decision table, placement included.
-
Reference —
Launch,vauban.launch.keep,vauban.opens.auto. -
The cdi#1015 example — the expert-group case, end to end.