This page covers the CDI 4.1 Lite patterns Vauban supports and how they integrate with the rest of the ecosystem. Every example compiles; the entire injection bytecode is generated at process-classes.
Scopes
CDI 4.1 Lite ships four standard scopes. Vauban supports all of them, plus @RequestScoped whenever a contextual transport is wired in.
| Scope | Semantics | Implemented |
|---|---|---|
|
One bean per JVM, no proxy. |
✅ |
|
One bean per container, normal-scope proxy. |
✅ |
|
One instance per injection point, no proxy. |
✅ (default) |
|
One instance per request, normal-scope proxy. |
✅ (with Cassini or Foy active) |
|
One instance per HTTP session. |
// TODO@user: confirm status with Foy / Cassini |
|
Normal-scoped beans ( |
Qualifiers
Discriminate multiple implementations of the same type.
import jakarta.inject.Qualifier;
import java.lang.annotation.*;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
public @interface Audit {}
@ApplicationScoped @Audit
public class AuditLogger implements Logger { /* ... */ }
@ApplicationScoped
public class Service {
@Inject @Audit Logger logger;
}
@Named, @Default, @Any are supported. Custom qualifiers with @Nonbinding members work too.
Producers and @Disposes
@ApplicationScoped
public class DataSourceProducer {
@Produces @ApplicationScoped
public DataSource dataSource(Config config) {
return DataSource.builder().url(config.url()).build();
}
public void close(@Disposes DataSource ds) {
ds.close();
}
}
The @Disposes callback runs when the producing bean is destroyed. Vauban emits a dedicated _ProducerFactory per producer method.
Events
public record OrderCreated(String id) {}
@ApplicationScoped
public class OrderService {
@Inject Event<OrderCreated> events;
public void create(String id) {
events.fire(new OrderCreated(id));
}
}
@ApplicationScoped
public class OrderListener {
public void onOrder(@Observes OrderCreated event) {
System.out.println("new order " + event.id());
}
}
Async observers (@ObservesAsync) are dispatched on the EventDispatcher’s internal `Executors.newVirtualThreadPerTaskExecutor(). See the threading model.
Interceptors
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Logged {}
@Logged
@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
public class LoggingInterceptor {
@AroundInvoke
public Object around(InvocationContext ctx) throws Exception {
long t0 = System.nanoTime();
try {
return ctx.proceed();
} finally {
System.out.printf("%s : %d ns%n", ctx.getMethod(), System.nanoTime() - t0);
}
}
}
@ApplicationScoped @Logged
public class Service { /* intercepted methods */ }
@AroundConstruct is also supported. Interceptor chains are resolved at compile time and stored in the generated _Factory.
Programmatic Instance<T>
@ApplicationScoped
public class Router {
@Inject Instance<Handler> handlers;
public Handler resolve(String name) {
return handlers.select(NamedLiteral.of(name)).get();
}
}
Instance<T> enables dynamic resolution from application code while keeping the injection grid static inside the container.
Integration with Cassini (Jakarta REST)
vauban-core is the underlying DI layer of Cassini. @Path resources are CDI beans; @Provider providers (MessageBodyReader/Writer) too. No configuration: Cassini consumes the Vauban BeanManager directly.
@Path("/orders")
@ApplicationScoped
public class OrderResource {
@Inject OrderService service;
@POST
public Response create(OrderRequest req) {
service.create(req.id());
return Response.created(URI.create("/orders/" + req.id())).build();
}
}
Integration with Foy (Jakarta Servlet)
Foy exposes servlets, filters and listeners as CDI beans. @RequestScoped follows the HTTP request/response lifecycle.
JUnit tests with @VaubanTest
@VaubanTest
class GreetingServiceTest {
@Inject GreetingService greeting;
@Test
void hello() {
assertEquals("Hello, Vauban!", greeting.hello("Vauban"));
}
}
The extension boots a minimal container per test class. See the JUnit reference.