Overview
Ravel integrates with CDI via Vauban Build-Compatible Extension (ravel-cdi-vauban), allowing you to inject configuration values directly into CDI beans with the @ConfigProperty annotation.
Setup
Add the dependency:
<dependency>
<groupId>io.vidocq.ravel</groupId>
<artifactId>ravel-cdi-vauban</artifactId>
<version>0.4.0-SNAPSHOT</version>
</dependency>
The extension is automatically discovered by CDI via ServiceLoader and Vauban’s Build-Compatible Extension mechanism.
Basic injection
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class DatabaseConfig {
@Inject
@ConfigProperty(name = "db.host")
String host;
@Inject
@ConfigProperty(name = "db.port")
int port;
@Inject
@ConfigProperty(name = "db.name")
String database;
}
The values are injected at bean creation time, converted to the target type.
With defaults
Use the defaultValue attribute to provide a fallback:
@Inject
@ConfigProperty(name = "app.timeout", defaultValue = "5000")
long timeoutMs;
@Inject
@ConfigProperty(name = "app.cache.enabled", defaultValue = "true")
boolean cacheEnabled;
If the configuration key is not found and no defaultValue is provided, deployment fails with DeploymentException.
Optional values
For optional configuration, use Optional<T>:
@Inject
@ConfigProperty(name = "feature.flag.experimental")
Optional<Boolean> experimentalFeature;
// Usage
if (experimentalFeature.isPresent()) {
boolean enabled = experimentalFeature.get();
// ...
}
Any injectable type, including plain String, can be wrapped in Optional:
@Inject
@ConfigProperty(name = "app.version")
Optional<String> version;
Supported types
All types supported by type converters can be injected:
-
Primitives:
int,long,boolean,float,double, etc. -
Boxes:
Integer,Long,Boolean,Float,Double, etc. -
Collections:
List<T>,Set<T>,Collection<T> -
Temporal:
Duration,LocalDate,LocalDateTime,Instant, etc. -
Custom types with implicit or registered converters
Field vs. constructor injection
Field injection is the canonical pattern for @ConfigProperty:
@ApplicationScoped
public class Config {
@Inject
@ConfigProperty(name = "key")
String value;
}
Constructor injection also works, but is less commonly used with config:
@ApplicationScoped
public class Config {
private final String value;
@Inject
public Config(@ConfigProperty(name = "key") String value) {
this.value = value;
}
}
Profile-aware injection
@ConfigProperty automatically respects configuration profiles:
app.db.host=prod-db.example.com
%dev.app.db.host=localhost
@Inject
@ConfigProperty(name = "app.db.host")
String dbHost;
With java -Dmp.config.profile=dev:
- dbHost = localhost
Validation at deployment time
Configuration values are validated when the container starts. If a required key is missing, DeploymentException is thrown:
@Inject
@ConfigProperty(name = "app.secret.key") // Required, no default
String secretKey;
$ java MyApplication
Exception in thread "main" jakarta.enterprise.inject.spi.DeploymentException:
Configuration key 'app.secret.key' not found
Use defaultValue or Optional<T> to make injection optional.
When the extension runs in the compiler NEW
Vauban can also run the extension while the application compiles: put ravel-cdi-vauban on the
annotation processor path (on Vidocq, the vidocq-runtime-ravel-config-extension-codegen bundle does it).
The extension then registers the beans that satisfy each @ConfigProperty and @ConfigProperties point,
so Vauban’s build-time validation accepts them, and an unsupported injection type fails the build.
It does not check the values there. They come from where the application runs — its configuration files,
the environment, the system properties of its JVM — which the compiler does not see. A missing key still
fails the container start, as above. The extension asks Vauban which of the two it is running in
(io.vidocq.vauban.api.ExtensionPhase).
Grouped configuration
A common pattern is grouping related config in a @Dependent bean:
@Dependent
public class DatabaseConfig {
@Inject
@ConfigProperty(name = "database.host")
String host;
@Inject
@ConfigProperty(name = "database.port")
int port;
@Inject
@ConfigProperty(name = "database.name")
String name;
}
@ApplicationScoped
public class MyApplication {
@Inject
DatabaseConfig dbConfig;
void start() {
// Use dbConfig.host, dbConfig.port, dbConfig.name
}
}
Example: Feature flags
@ApplicationScoped
public class Features {
@Inject
@ConfigProperty(name = "feature.cache", defaultValue = "true")
boolean cacheEnabled;
@Inject
@ConfigProperty(name = "feature.metrics", defaultValue = "false")
boolean metricsEnabled;
@Inject
@ConfigProperty(name = "feature.tracing")
Optional<Boolean> tracingEnabled;
public boolean isCacheEnabled() {
return cacheEnabled;
}
public boolean isMetricsEnabled() {
return metricsEnabled;
}
public boolean isTracingEnabled() {
return tracingEnabled.orElse(false);
}
}
Example: Multi-environment API client
# microprofile-config.properties
api.base.url=https://api.example.com
api.timeout=5000
api.retry.attempts=3
%dev.api.base.url=http://localhost:8080
%dev.api.timeout=10000
%dev.api.retry.attempts=1
@ApplicationScoped
public class ApiClient {
@Inject
@ConfigProperty(name = "api.base.url")
String baseUrl;
@Inject
@ConfigProperty(name = "api.timeout")
long timeoutMs;
@Inject
@ConfigProperty(name = "api.retry.attempts", defaultValue = "3")
int retryAttempts;
public void call() {
// baseUrl, timeoutMs, retryAttempts are injected
// and vary by profile
}
}
Other CDI containers NEW
Ravel does not need Vauban. The jars run unchanged under another CDI container, and two integration-test modules, grouped under ravel-it-other-containers, prove it on every build (ravel#25), next to the official TCK, which already runs on Weld (TCK):
| Module | What it runs |
|---|---|
|
Weld SE 6.0 (CDI 4.1), class path, no Vauban: |
|
A WAR on Open Liberty 26.0.0.10, MicroProfile 7 distribution (CDI 4.0, Jakarta REST 3.1), with Liberty’s |
Neither module is published. ravel-cdi-vauban ships no beans.xml, on purpose: every bean it adds is synthetic, made by its build compatible extension, except RavelConfigProducer. A container that does not scan implicit archives, such as Weld SE by default, does not discover that producer, and the extension then synthesises the @Default Config bean itself.
|
Deploying on an application server
|