Modern, JVM-based framework for building modular, easily testable microservice and serverless applications with compile-time DI and fast startup.
—
Micronaut's configuration system provides type-safe property binding, environment-specific configuration loading, and hot-reload support for development with comprehensive validation and nested object support.
Bind configuration properties to typed objects with validation and nested configuration support.
/**
* Basic configuration properties
*/
@ConfigurationProperties("app.database")
public class DatabaseConfiguration {
private String host = "localhost";
private int port = 5432;
private String username;
private String password;
private String database;
private boolean ssl = false;
// getters and setters
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
// ... other getters and setters
}
/**
* Configuration with validation
*/
@ConfigurationProperties("app.server")
public class ServerConfiguration {
@NotBlank
private String name;
@Min(1024)
@Max(65535)
private int port = 8080;
@Email
private String adminEmail;
@URL
private String baseUrl;
@Valid
private SslConfig ssl;
// getters and setters
}
/**
* Nested configuration
*/
public static class SslConfig {
private boolean enabled = false;
private String keyStore;
private String keyStorePassword;
private String protocol = "TLS";
// getters and setters
}Load different configurations based on the active environment.
/**
* Environment-specific configuration
*/
@ConfigurationProperties("cache")
@Requires(env = Environment.DEVELOPMENT)
public class DevelopmentCacheConfiguration {
private String type = "caffeine";
private long maxSize = 100;
private Duration expireAfterWrite = Duration.ofMinutes(5);
// getters and setters
}
@ConfigurationProperties("cache")
@Requires(env = Environment.PRODUCTION)
public class ProductionCacheConfiguration {
private String type = "redis";
private String host = "redis.example.com";
private int port = 6379;
private Duration expireAfterWrite = Duration.ofHours(1);
// getters and setters
}
/**
* Conditional configuration based on properties
*/
@ConfigurationProperties("monitoring")
@Requires(property = "monitoring.enabled", value = "true")
public class MonitoringConfiguration {
private String endpoint;
private Duration interval = Duration.ofMinutes(1);
private List<String> metrics = new ArrayList<>();
// getters and setters
}Inject individual property values into beans and methods.
/**
* Property value injection in beans
*/
@Singleton
public class ApiService {
@Value("${api.base-url}")
private String baseUrl;
@Value("${api.timeout:30s}")
private Duration timeout;
@Value("${api.retries:3}")
private int maxRetries;
@Value("${api.enabled:true}")
private boolean enabled;
public ApiService(@Value("${api.key}") String apiKey,
@Value("${api.version:v1}") String version) {
// Constructor injection of properties
}
}
/**
* Property injection in configuration methods
*/
@Factory
public class ServiceFactory {
@Bean
@Singleton
public ExternalService externalService(@Value("${external.service.url}") String url,
@Value("${external.service.token}") String token) {
return new ExternalService(url, token);
}
}Support for configuration that can change at runtime with refresh capabilities.
/**
* Refreshable configuration
*/
@ConfigurationProperties("feature-flags")
@Refreshable
public class FeatureFlagConfiguration {
private Map<String, Boolean> flags = new HashMap<>();
private boolean debugMode = false;
public boolean isFeatureEnabled(String feature) {
return flags.getOrDefault(feature, false);
}
// getters and setters
}
/**
* Using refreshable configuration
*/
@Singleton
public class FeatureService {
private final FeatureFlagConfiguration featureConfig;
public FeatureService(FeatureFlagConfiguration featureConfig) {
this.featureConfig = featureConfig;
}
public boolean isNewUiEnabled() {
return featureConfig.isFeatureEnabled("new-ui");
}
}
/**
* Configuration refresh event handling
*/
@Singleton
public class ConfigurationRefreshListener {
@EventListener
public void onRefresh(RefreshEvent event) {
log.info("Configuration refreshed: {}", event.getSource());
// React to configuration changes
}
}Configure collections and complex nested structures.
/**
* List and map configuration
*/
@ConfigurationProperties("app")
public class ApplicationConfiguration {
private List<String> allowedOrigins = new ArrayList<>();
private Map<String, String> headers = new HashMap<>();
private List<ServerConfig> servers = new ArrayList<>();
// getters and setters
public List<String> getAllowedOrigins() { return allowedOrigins; }
public void setAllowedOrigins(List<String> allowedOrigins) {
this.allowedOrigins = allowedOrigins;
}
public Map<String, String> getHeaders() { return headers; }
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
}
/**
* Nested object in lists
*/
public static class ServerConfig {
private String name;
private String host;
private int port;
private List<String> protocols = new ArrayList<>();
// getters and setters
}
/**
* Multiple configuration instances using @EachProperty
*/
@EachProperty("databases")
@ConfigurationProperties("databases")
public class DatabaseInstanceConfiguration {
private String name;
private String url;
private String driver;
private int maxConnections = 10;
// This will create separate configuration instances for:
// databases.primary.*, databases.secondary.*, etc.
// getters and setters
}Use builder pattern for complex configuration objects.
/**
* Configuration with builder support
*/
@ConfigurationProperties("http-client")
public class HttpClientConfiguration {
@ConfigurationBuilder(prefixes = "")
private ConnectionPoolConfiguration connectionPool =
ConnectionPoolConfiguration.builder().build();
@ConfigurationBuilder(prefixes = "ssl")
private SslConfiguration ssl = SslConfiguration.builder().build();
private Duration readTimeout = Duration.ofSeconds(30);
private boolean followRedirects = true;
// getters and setters
}
/**
* Builder-based configuration classes
*/
public class ConnectionPoolConfiguration {
private int maxConnections = 10;
private Duration idleTimeout = Duration.ofMinutes(5);
private boolean keepAlive = true;
public static Builder builder() {
return new Builder();
}
public static class Builder {
private ConnectionPoolConfiguration config = new ConnectionPoolConfiguration();
public Builder maxConnections(int maxConnections) {
config.maxConnections = maxConnections;
return this;
}
public Builder idleTimeout(Duration idleTimeout) {
config.idleTimeout = idleTimeout;
return this;
}
// other builder methods
public ConnectionPoolConfiguration build() {
return config;
}
}
// getters
}Configure multiple sources for properties with precedence handling.
/**
* Custom property source
*/
@Singleton
@Primary
public class DatabasePropertySource implements PropertySource {
@Override
public String getName() {
return "database-config";
}
@Override
public Object get(String key) {
// Load from database
return configRepository.findByKey(key);
}
@Override
public Iterator<String> iterator() {
return configRepository.findAllKeys().iterator();
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}
/**
* Property source loader for custom formats
*/
@Singleton
public class YamlPropertySourceLoader implements PropertySourceLoader {
@Override
public Set<String> getExtensions() {
return Set.of("yml", "yaml");
}
@Override
public Optional<PropertySource> load(String resourceName, InputStream inputStream) {
try {
// Parse YAML and create property source
Map<String, Object> properties = yamlParser.parse(inputStream);
return Optional.of(PropertySource.of(resourceName, properties));
} catch (Exception e) {
return Optional.empty();
}
}
}Validate configuration at startup with custom validators.
/**
* Configuration with Bean Validation annotations
*/
@ConfigurationProperties("payment")
public class PaymentConfiguration {
@NotBlank(message = "Payment provider is required")
private String provider;
@Valid
@NotNull
private ProviderConfig config;
@Min(value = 1, message = "Timeout must be at least 1 second")
@Max(value = 300, message = "Timeout cannot exceed 5 minutes")
private int timeoutSeconds = 30;
@Pattern(regexp = "^[A-Z]{3}$", message = "Currency must be 3-letter ISO code")
private String defaultCurrency = "USD";
// getters and setters
}
/**
* Custom validator for configuration
*/
@Singleton
public class PaymentConfigurationValidator {
@EventListener
public void validateOnStartup(StartupEvent event) {
ApplicationContext context = event.getSource();
PaymentConfiguration config = context.getBean(PaymentConfiguration.class);
if (!isValidProvider(config.getProvider())) {
throw new ConfigurationException(
"Invalid payment provider: " + config.getProvider());
}
}
private boolean isValidProvider(String provider) {
return Set.of("stripe", "paypal", "square").contains(provider.toLowerCase());
}
}// Core configuration interfaces
public interface Environment extends PropertyResolver {
Set<String> getActiveNames();
Collection<PropertySource> getPropertySources();
Environment addPropertySource(PropertySource propertySource);
Environment removePropertySource(PropertySource propertySource);
static final String DEVELOPMENT = "dev";
static final String TEST = "test";
static final String PRODUCTION = "prod";
}
public interface PropertySource extends Iterable<String> {
String getName();
Object get(String key);
int getOrder();
static PropertySource of(String name, Map<String, Object> values);
static PropertySource of(Map<String, Object> values);
}
public interface PropertyResolver {
boolean containsProperty(String name);
boolean containsProperties(String name);
<T> Optional<T> getProperty(String name, Class<T> requiredType);
<T> T getProperty(String name, Class<T> requiredType, T defaultValue);
String getProperty(String name, String defaultValue);
Optional<String> getProperty(String name);
}
// Configuration binding
public interface PropertyPlaceholderResolver {
Optional<String> resolvePlaceholders(String str);
String resolveRequiredPlaceholders(String str);
}
public interface ConversionService {
<T> Optional<T> convert(Object object, Class<T> targetType);
<T> Optional<T> convert(Object object, Argument<T> targetType);
<T> T convert(Object object, Class<T> targetType, T defaultValue);
}
// Configuration events
public class RefreshEvent extends ApplicationEvent {
public RefreshEvent(Object source) {
super(source);
}
}
public class ConfigurationChangedEvent extends ApplicationEvent {
private final String propertyName;
private final Object oldValue;
private final Object newValue;
// constructors and getters
}
// Property source loaders
public interface PropertySourceLoader {
Set<String> getExtensions();
Optional<PropertySource> load(String resourceName, InputStream inputStream);
Map<String, PropertySourceLoader> getLoadersMap();
}
// Configuration exceptions
public class ConfigurationException extends RuntimeException {
public ConfigurationException(String message);
public ConfigurationException(String message, Throwable cause);
}
public class PropertyNotFoundException extends ConfigurationException {
public PropertyNotFoundException(String property);
}Install with Tessl CLI
npx tessl i tessl/maven-micronaut