This module provides a writable file-based configuration source for MicroProfile Config. It extends the standard MicroProfile Config API to support reading and writing configuration properties from/to a file at runtime.
MicroProfile Config normally treats configuration sources as read-only. This module adds a WritableFileConfigSource that:
A ConfigSource implementation that stores configuration in a properties file and supports write operations.
A utility class that simplifies creating and initializing configuration files with default values. This is especially useful for:
The file location is determined by (in order of priority):
config.file.nameconfig.file.name in META-INF/microprofile-config.propertiesconfig/application.properties// Get the config source
Optional<WritableFileConfigSource> source =
ConfigSourceUtil.activeWritableFileConfigSource();
if (source.isPresent()) {
WritableFileConfigSource config = source.get();
// Read a property
String value = config.getValue("app.name");
// Set a new property (automatically saved to file)
config.setProperty("app.version", "1.0.0");
// Update an existing property
config.setProperty("app.name", "MyApp");
// Remove a property (automatically saved to file)
config.removeProperty("old.property");
// Get all property names
Set<String> propertyNames = config.getPropertyNames();
// Get all properties as a map
Map<String, String> properties = config.getProperties();
}
getValue(String key): Returns the value for the given property key, or null if not foundgetPropertyNames(): Returns all property keysgetProperties(): Returns an unmodifiable map of all propertiessetProperty(String key, String value): Sets a property and saves to fileremoveProperty(String key): Removes a property and saves to filesave(): Manually saves all properties to file (called automatically by set/remove)getName(): Returns the name of this config source including the file pathgetOrdinal(): Returns 500 (higher than default sources)A utility class to find the active WritableFileConfigSource in the current MicroProfile Config.
// Find the writable config source
Optional<WritableFileConfigSource> optSource =
ConfigSourceUtil.activeWritableFileConfigSource();
optSource.ifPresent(source -> {
// Use the config source
source.setProperty("key", "value");
});
activeWritableFileConfigSource(): Returns the first WritableFileConfigSource found in the active config sources, or an empty Optional if none existsThe easiest way to create a config file with default values in your project root:
import de.ruu.lib.util.config.mp.WritableFileConfigSource;
// Create postgresutil.config in project root with default values
WritableFileConfigSource config =
PostgresUtil.initializePostgresUtilConfig();
// The file is now created at: postgresutil.config
// It contains default values for postgres connection settings
// Read values
String host = config.getValue("postgres.host"); // "localhost"
String port = config.getValue("postgres.port"); // "5432"
// Modify values (automatically saved to file)
config.
setProperty("postgres.host","db.example.com");
config.
setProperty("postgres.port","5433");
import java.util.HashMap;
import java.util.Map;
// Define your default values
Map<String, String> defaults = new HashMap<>();
defaults.put("app.name", "MyApp");
defaults.put("app.version", "1.0.0");
defaults.put("app.debug", "false");
// Create config file in project root
WritableFileConfigSource config = ConfigFileInitializer.initializeConfigFile(
"myapp.config",
defaults
);
// If file already exists, only missing properties are added
// Existing values are never overwritten
The ConfigFileInitializer automatically preserves existing values:
// First run: creates file with defaults
ConfigFileInitializer.initializePostgresUtilConfig();
// Creates: postgresutil.config with postgres.host=localhost
// User manually edits file: postgres.host=production-db
// Second run: preserves user changes, adds any new defaults
ConfigFileInitializer.initializePostgresUtilConfig();
// Result: postgres.host=production-db (user value preserved)
For application updates where new config properties are needed:
WritableFileConfigSource config = /* ... */;
Map<String, String> requiredDefaults = new HashMap<>();
requiredDefaults.put("new.feature.enabled", "true");
requiredDefaults.put("new.cache.size", "1000");
// Adds only missing properties
int added = ConfigFileInitializer.ensureRequiredProperties(
config,
requiredDefaults
);
System.out.println("Added " + added + " new properties");
WritableFileConfigSource is registered via the Java ServiceLoader mechanism (declared in module-info.java)config.file.name property from system properties or bootstrap configThe configuration file uses standard Java properties format:
# Comment
key1=value1
key2=value2
app.name=MyApplication
app.version=1.0.0
The WritableFileConfigSource uses a ConcurrentHashMap internally to store properties, making it thread-safe for concurrent reads and writes.
This config source integrates with the standard MicroProfile Config API:
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
// Standard MP Config usage
Config config = ConfigProvider.getConfig();
String value = config.getValue("app.name", String.class);
// The WritableFileConfigSource is automatically included
// and can override values from other sources due to its high ordinal
This module requires:
microprofile-config-api)jakarta.enterprise.cdi-api)The module is defined in module-info.java:
module de.ruu.lib.util.config {
exports de.ruu.lib.util.config.mp;
provides org.eclipse.microprofile.config.spi.ConfigSource
with de.ruu.lib.util.config.mp.WritableFileConfigSource;
requires de.ruu.lib.util;
requires transitive jakarta.cdi;
requires transitive microprofile.config.api;
requires static lombok;
requires org.slf4j;
}
The module includes comprehensive JUnit tests using Hamcrest matchers:
Tests for the main config source implementation:
Tests for the utility class:
# Run all tests in the module
mvn test -pl lib/mp_config
# Run a specific test class
mvn test -pl lib/mp_config -Dtest=WritableFileConfigSourceTest
// Load settings
WritableFileConfigSource config = /* ... */;
String theme = config.getValue("ui.theme");
// Save user preference
config.setProperty("ui.theme", "dark");
// Update configuration without restarting the application
config.setProperty("cache.size", "500");
config.setProperty("feature.enabled", "true");
// Remove deprecated properties
config.removeProperty("old.setting");
// Add new properties with default values
if (config.getValue("new.setting") == null) {
config.setProperty("new.setting", "default");
}
The implementation carefully avoids using ConfigProvider.getConfig() during initialization to prevent bootstrap recursion. Instead, it:
META-INF/microprofile-config.properties directlyIf the configured file doesn’t exist:
With an ordinal of 500, this config source:
Actually, system properties have ordinal 400, so this source (500) will override them. Adjust the ordinal if different behavior is needed.
ConfigSourceUtil.activeWritableFileConfigSource() to find the config source rather than creating new instancesmodule.component.setting)Part of the r-uu space-02 project.