MicroProfile Config implementation for GuicedEE applications using SmallRye Config and Google Guice.
Inject configuration values with @ConfigProperty, resolve from environment variables, system properties, and META-INF/microprofile-config.properties β all wired automatically through SPI discovery and Guice bindings.
Built on SmallRye Config Β· MicroProfile Config Β· Google Guice Β· Vert.x Β· JPMS module com.guicedee.microprofile.config Β· Java 25+
<dependency>
<groupId>com.guicedee.microprofile</groupId>
<artifactId>config</artifactId>
</dependency>Gradle (Kotlin DSL)
implementation("com.guicedee.microprofile:config:2.0.1-SNAPSHOT")- CDI-style injection β
@ConfigProperty(name = "key")with automatic type conversion forString,Boolean,Integer,Long,Double,Float, andOptional<T>wrappers - Standards-compliant β implements
org.eclipse.microprofile.configAPIs and annotations via SmallRye Config - Deterministic source ordering β environment variables (highest), system properties, and classpath
META-INF/microprofile-config.properties(lowest) - Custom converters β register
Converter<T>implementations viaServiceLoaderfor application-specific types - Profile support β profile-specific properties (e.g.
%dev.key=value) resolved by SmallRye Config - Guice-native β
SmallRyeConfigis bound as a singleton viaSmallRyeConfigProvider;@ConfigPropertyfields are scanned and bound at startup - Vert.x-aware initialization β config is built on a Vert.x worker thread to avoid blocking the event loop
- JPMS-ready β named module with proper exports, provides, and opens directives
Step 1 β Add a microprofile-config.properties file:
# src/main/resources/META-INF/microprofile-config.properties
messaging.enabled=true
messaging.bootstrap.servers=localhost:9092
liveness.port=8081Step 2 β Inject configuration values:
import org.eclipse.microprofile.config.inject.ConfigProperty;
public class MessagingService {
@ConfigProperty(name = "messaging.enabled", defaultValue = "true")
boolean enabled;
@ConfigProperty(name = "messaging.bootstrap.servers")
String bootstrapServers;
public void start() {
if (enabled) {
// use bootstrapServers
}
}
}Step 3 β Register via JPMS:
module my.app {
requires com.guicedee.microprofile.config;
}The MicroProfileConfigContext initializes SmallRye Config automatically during IGuicePreStartup, and MicroProfileConfigBinder scans for all @ConfigProperty fields and binds them into Guice.
flowchart TD
n1["IGuiceContext.instance()"]
n2["IGuiceConfigurator hooks"]
n1 --> n2
n3["ScanConfig<br/>enables field, annotation, classpath scanning"]
n2 --> n3
n4["IGuicePreStartup hooks"]
n1 --> n4
n5["MicroProfileConfigContext<br/>builds SmallRyeConfig on Vert.x worker thread"]
n4 --> n5
n6["addDefaultSources()<br/>env vars, system props, microprofile-config.properties"]
n5 --> n6
n7["addDiscoveredSources()<br/>ServiceLoader-discovered ConfigSource SPIs"]
n5 --> n7
n8["addDiscoveredConverters()<br/>ServiceLoader-discovered Converter<T> SPIs"]
n5 --> n8
n9["addDiscoveredInterceptors()<br/>ConfigSourceInterceptor SPIs"]
n5 --> n9
n10["IGuiceModule hooks"]
n1 --> n10
n11["MicroProfileConfigBinder<br/>Guice AbstractModule"]
n10 --> n11
n12["bind SmallRyeConfig via SmallRyeConfigProvider<br/>singleton"]
n11 --> n12
n13["scan @ConfigProperty fields via ClassGraph"]
n11 --> n13
n14["bind each field type: String, Boolean, Integer, Long, Double, Float, Optional<T>"]
n11 --> n14
n15["InjectionPointProvider"]
n1 --> n15
n16["InjectionPointProvision<br/>registers @ConfigProperty as a Guice injection point"]
n15 --> n16
MicroProfile Config defines a composite of sources with numeric ordinals. Higher ordinal wins when keys overlap:
| Source | Ordinal | Example |
|---|---|---|
| Environment variables | 300 | MESSAGING_ENABLED=true |
| System properties | 200 | -Dmessaging.enabled=true |
META-INF/microprofile-config.properties |
100 | messaging.enabled=true |
Environment variable names follow MicroProfile Config rules:
- Dots (
.) and hyphens (-) are replaced with underscores (_) - Names are uppercased
- e.g.
messaging.bootstrap.serversβMESSAGING_BOOTSTRAP_SERVERS
SmallRye Config supports profile-specific properties using the %profile. prefix:
# META-INF/microprofile-config.properties
db.url=jdbc:postgresql://prod-host:5432/mydb
# Profile-specific override
%dev.db.url=jdbc:postgresql://localhost:5432/mydbActivate a profile with mp.config.profile=dev (system property or environment variable).
Inject the SmallRyeConfig instance directly for programmatic lookups:
import jakarta.inject.Inject;
import io.smallrye.config.SmallRyeConfig;
public class HealthProbe {
@Inject
SmallRyeConfig config;
public int livenessPort() {
return config.getOptionalValue("liveness.port", Integer.class).orElse(8081);
}
}Or use the standard MicroProfile Config interface:
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
Config config = ConfigProvider.getConfig();
String value = config.getValue("messaging.enabled", String.class);MicroProfileConfigBinder scans all classes with @ConfigProperty-annotated fields and creates Guice bindings for each:
| Field type | Binding |
|---|---|
String |
Direct value from config |
boolean / Boolean |
Parsed via Boolean.parseBoolean() |
int / Integer |
Parsed via Integer.parseInt() |
long / Long |
Parsed via Long.parseLong() |
double / Double |
Parsed via Double.parseDouble() |
float / Float |
Parsed via Float.parseFloat() |
Optional<String> |
Wrapped optional |
Optional<Boolean> |
Wrapped optional |
Optional<Integer> |
Wrapped optional |
Optional<Long> |
Wrapped optional |
Optional<Double> |
Wrapped optional |
Optional<Float> |
Wrapped optional |
Default values are supported via @ConfigProperty(defaultValue = "...").
Register custom Converter<T> implementations via ServiceLoader for application-specific types:
import org.eclipse.microprofile.config.spi.Converter;
import java.time.Duration;
public class DurationConverter implements Converter<Duration> {
@Override
public Duration convert(String value) {
if (value.endsWith("ms")) return Duration.ofMillis(Long.parseLong(value.replace("ms", "")));
if (value.endsWith("s")) return Duration.ofSeconds(Long.parseLong(value.replace("s", "")));
throw new IllegalArgumentException("Unsupported duration format: " + value);
}
}Register via META-INF/services/org.eclipse.microprofile.config.spi.Converter:
com.example.DurationConverter
And then with JPMS:
module my.app {
provides org.eclipse.microprofile.config.spi.Converter
with com.example.DurationConverter;
}All SPIs are discovered via ServiceLoader. Register implementations with JPMS provides...with or META-INF/services.
| SPI | Purpose |
|---|---|
IGuicePreStartup |
MicroProfileConfigContext β builds the SmallRyeConfig instance |
IGuiceModule |
MicroProfileConfigBinder β scans @ConfigProperty fields and binds to Guice |
IGuiceConfigurator |
ScanConfig β enables classpath, annotation, and field scanning |
InjectionPointProvider |
InjectionPointProvision β registers @ConfigProperty as an injection point |
ConfigSource (MicroProfile) |
Add custom config sources with custom ordinals |
Converter<T> (MicroProfile) |
Register custom type converters |
ConfigSourceInterceptor (SmallRye) |
Intercept and transform config values |
flowchart LR
com_guicedee_microprofile_config["com.guicedee.microprofile.config"]
com_guicedee_microprofile_config --> io_smallrye_config_core["io.smallrye.config.core<br/>SmallRye Config β MicroProfile Config implementation"]
com_guicedee_microprofile_config --> com_guicedee_vertx["com.guicedee.vertx<br/>Vert.x lifecycle β worker thread initialization"]
com_guicedee_microprofile_config --> com_guicedee_client["com.guicedee.client<br/>GuicedEE client β SPI contracts, IGuiceContext"]
com_guicedee_microprofile_config --> com_google_guice["com.google.guice<br/>Guice DI β injection bindings"]
| Class | Package | Role |
|---|---|---|
MicroProfileConfigContext |
config |
IGuicePreStartup β builds SmallRyeConfig on a Vert.x worker thread at startup |
MicroProfileConfigBinder |
implementations |
IGuiceModule β scans @ConfigProperty fields and creates Guice bindings per type |
SmallRyeConfigProvider |
implementations |
Guice Provider<SmallRyeConfig> β returns the shared config instance |
InjectionPointProvision |
implementations |
InjectionPointProvider β registers @ConfigProperty for Guice injection point processing |
ScanConfig |
implementations |
IGuiceConfigurator β enables classpath, annotation, and field scanning |
Module name: com.guicedee.microprofile.config
The module:
- exports
com.guicedee.microprofile.config - requires transitive
io.smallrye.config.core,com.guicedee.vertx - provides
IGuicePreStartupwithMicroProfileConfigContext - provides
IGuiceModulewithMicroProfileConfigBinder - provides
InjectionPointProviderwithInjectionPointProvision - provides
IGuiceConfiguratorwithScanConfig - opens
com.guicedee.microprofile.config.implementationstocom.google.guice
In non-JPMS environments, META-INF/services discovery still works.
import com.guicedee.client.IGuiceContext;
import com.google.inject.Injector;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ConfigTest {
@Test
void configPropertyFieldsAreInjected() {
Injector injector = IGuiceContext.getContext().inject();
MyConfigBean bean = injector.getInstance(MyConfigBean.class);
assertNotNull(bean.getServerHost());
}
}Provide test configuration in src/test/resources/META-INF/microprofile-config.properties:
server.host=localhost
server.port=8080Issues and pull requests are welcome β please add tests for new type converters, config sources, or binding changes.