This page shows how to write a first @ApplicationScoped bean, wire it via @Inject, and bootstrap the container. The build generates every byte of injection bytecode: no dynamic proxies, no runtime reflection.
Prerequisites
-
Java 25 — Temurin recommended. Module path enabled.
-
Maven 3.9.16 — pinned via
.sdkmanrcat the repo root. -
Strict Java Modules: one
module-info.javaper Maven module.
Maven coordinates
<dependencies>
<dependency>
<groupId>io.vidocq.vauban</groupId>
<artifactId>vauban-api</artifactId>
<version>0.4.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.vidocq.vauban</groupId>
<artifactId>vauban-core</artifactId>
<version>0.4.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.vidocq.vauban</groupId>
<artifactId>vauban-processor</artifactId>
<version>0.4.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.vidocq.vauban</groupId>
<artifactId>vauban-maven-plugin</artifactId>
<version>0.4.0-SNAPSHOT</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
First @ApplicationScoped bean
package io.example;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class GreetingService {
public String hello(String name) {
return "Hello, " + name + "!";
}
}
First @Inject
package io.example;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class HelloApp {
@Inject GreetingService greeting;
public void run() {
System.out.println(greeting.hello("Vauban"));
}
}
|
Both field and constructor injection are supported. Constructor injection is recommended to ease unit testing outside the container. |
Minimal module-info.java
module io.example {
requires jakarta.cdi;
requires io.vidocq.vauban.core;
exports io.example;
}
Container bootstrap
import io.vidocq.vauban.core.container.VaubanContainer;
void main() {
try (var container = VaubanContainer.builder().scanLocal().build()) {
var app = container.select(HelloApp.class);
app.run();
}
}
try-with-resources shuts the container down cleanly: @PreDestroy callbacks run, contexts are purged, and @BeforeDestroyed(ApplicationScoped.class) then @Destroyed(ApplicationScoped.class) events are fired.
Build and run
sdk env
./mvnw -ntp install -DskipTests
java --module-path target/modules --module io.example/io.example.Main
|
To run the application inside a Vauban layer instead of the boot layer — which is what lets a normal-scoped
Your |
The annotation processor has emitted the _Factory classes for each bean, and the Maven plugin’s generate goal has written the META-INF/vauban-beans.list bean index — both land in ${project.build.outputDirectory} (target/classes). At startup Vauban instantiates those factories and calls the generated methods: nothing reflects into your classes to build or inject a bean.
Three bean discovery modes
Vauban ships three strategies, depending on context:
| Mode | Use case | API |
|---|---|---|
|
Standard single-module application |
|
|
Multi-module application with transitive CDI dependencies |
|
|
Unit tests, programmatic selection |
|
Runnable examples
Everything above is a fragment. vauban-examples/ in the repository holds the working versions,
each module carrying one thing worth seeing. They are built by the reactor, so ./mvnw install on
the project runs them all.
| Module | What it shows |
|---|---|
|
An ordinary library module: beans, producers, a |
|
The application that consumes it — the smallest complete setup, on the module path. |
|
The same library shipped as an sjar: exported packages stay in clear text, everything else is encrypted, and the Vauban class loader decrypts at definition. A service can expose a clear interface over an encrypted implementation. |
|
The integration tests that run the above as real applications rather than unit tests. |
|
The one to read first if you care about proxies. A CDI-agnostic third-party library and an
application that produces four of its types, covering every proxying case in one runnable place:
a fully-public class and an interface (proxied from the producer’s package), a type with a
package-private member and one with no accessible constructor (proxy placed inside the library’s
own package by the class loader), an intercepted bean, and a nested bean. Zero |
|
The packaging case the pair above does not cover: a dependency shipped with no
|
|
The last resort, for when no class loader will be there to place a proxy — a GraalVM native
image defines its classes at build time. |
Next step
-
Usage patterns — qualifiers, producers, events, interceptors, and which proxy you get in each case.
-
Concepts — bean, scope, qualifier, BeanManager.