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 .sdkmanrc at the repo root.

  • Strict JPMS: one module-info.java per 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

scanLocal()

Standard single-module application

Vauban.builder().scanLocal().build()

scanClasspath()

Multi-module application with transitive CDI dependencies

Vauban.builder().scanClasspath().build()

addBeanClass(…​)

Unit tests, programmatic selection

Vauban.builder().addBeanClass(MyBean.class).build()

Next step

  • Usage patterns — qualifiers, producers, events, interceptors.

  • Concepts — bean, scope, qualifier, BeanManager.