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 JPMS: one
module-info.javaper Maven module.
Maven coordinates
<dependencies>
<dependency>
<groupId>io.vidocq.vauban</groupId>
<artifactId>vauban-api</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.vidocq.vauban</groupId>
<artifactId>vauban-core</artifactId>
<version>0.1.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.vidocq.vauban</groupId>
<artifactId>vauban-processor</artifactId>
<version>0.1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.vidocq.vauban</groupId>
<artifactId>vauban-maven-plugin</artifactId>
<version>0.1.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.api;
exports io.example;
}
Container bootstrap
import io.vidocq.vauban.api.Vauban;
void main() {
try (var container = Vauban.bootstrap()) {
var app = container.select(HelloApp.class).get();
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
The Maven plugin has emitted the _Factory classes for each bean into target/generated-sources/. No reflection runs at startup: Vauban merely instantiates those factories then invokes the generated methods.
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 |
|
Next step
-
Usage patterns — qualifiers, producers, events, interceptors.
-
Concepts — bean, scope, qualifier, BeanManager.