yacl4j is a configuration library for Java highly inspired by cfg4j and Spring Boot.
yacl4j:
- is open source;
- is easy to use and extend;
- is heavily based on the well-known and battle-tested Jackson library;
- supports hierarchical configurations by design;
- supports configurations in Yaml, Json and Properties format;
- supports configurations from file, classpath, system properties, environment variables and user-defined sources;
- supports placeholders resolution.
Set up your favorite dependency management tool:
<dependencies>
<dependency>
<groupId>com.github.fabriziocucci</groupId>
<artifactId>yacl4j-core</artifactId>
<version>0.9.2</version>
</dependency>
</dependencies>dependencies {
compile group: "com.github.fabriziocucci", name:"yacl4j-core", version: "0.9.2"
}yacl4j API is really small and can be easily described with an example:
interface MyConfiguration {
String getProperty();
MyNestedConfiguration getMyNestedConfiguration();
interface MyNestedConfiguration {
String getNestedProperty();
}
}MyConfiguration myConfiguration = ConfigurationBuilder.newBuilder() // #0
.source().fromFile(new File("some-path/application.yaml")) // #1
.source().fromFileOnClasspath("application.yaml") // #2
.source().fromFileOnPath("/Users/yacl4j/application.yaml") // #3
.source().fromSystemProperties() // #4
.source().fromEnvironmentVariables() // #5
.source(new MyCustomConfigurationSource()); // #6
.build(MyConfiguration.class); // #7In the previous example:
- at line 0, we are creating a new ConfigurationBuilder;
- at lines 1-6, we are adding 6 configuration sources, in increasing order of priority;
- at line 7, we are building the configuration bean based on the MyConfiguration interface;
- if one property is defined in multiple sources, the source with higher priority win.
yacl4j is heavily based on Jackson (at this stage) and, unfortunately, Jackson does not support default values in interfaces...yet. Disappointed? A bit. In trouble? No way. You just need to be a little bit more verbose:
public class MyConfiguration {
private String property = "42";
public String getProperty() {
return property;
}
}yacl4j supports placeholders resolution with the syntax ${relaxed-json-pointer}.
Let's consider an example:
greeting: Hello ${name}
name: yacl4jThe above configuration becomes:
greeting: Hello yacl4j
name: yacl4jLet's consider an example:
greeting: Hello ${person/name}
person:
name: yacl4jThe above configuration becomes:
greeting: Hello yacl4j
person:
name: yacl4jLet's consider an example:
greeting: Hello ${persons/0}
persons:
- yacl4jThe above configuration becomes:
greeting: Hello yacl4j
persons:
- yacl4jLet's consider an example:
object:
property: value
myObject: ${object}The above configuration becomes:
object:
property: value
myObject:
object:
property: valueLet's consider an example:
array:
- value
myArray: ${array} The above configuration becomes:
array:
- value
myArray:
array:
- valueFor a list of all supported placeholders check the PlaceholderResolver test.
yacl4j supports hierarchical configurations by design. Properties are not really hierarchical, so yacl4j leverages the Json Pointer RFC to transform properties-based configurations into hierarchical ones.
Let's consider an example:
java.runtime.name=Java(TM) SE Runtime Environment
java.runtime.version=1.8.0_77-b03
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
java.vm.vendor=Oracle Corporation
java.vm.version=25.77-b03The above configuration becomes:
java.runtime.name: Java(TM) SE Runtime Environment
java.runtime.version: 1.8.0_77-b03
java.vm.name: Java HotSpot(TM) 64-Bit Server VM
java.vm.vendor: Oracle Corporation
java.vm.version: 25.77-b03Mmmmm...that doesn't look really "hierarchical" to me! Let's change a little bit the properties:
java/runtime/name=Java(TM) SE Runtime Environment
java/runtime/version=1.8.0_77-b03
java/vm/name=Java HotSpot(TM) 64-Bit Server VM
java/vm/vendor=Oracle Corporation
java/vm/version=25.77-b03Hey, did we just replace all '.' with '/' ? Yes, indeed! This is because yacl4j is currently based on the Json Pointer RFC with one simple exception: the leading '/' is optional.
The above configuration becomes:
java:
runtime:
name: Java(TM) SE Runtime Environment
version: 1.8.0_77-b03
vm:
name: Java HotSpot(TM) 64-Bit Server VM
vendor: Oracle Corporation
version: 25.77-b03Better, right? So, it's really easy to transform flat Properties into hierarchical configurations.
My suggestion? Just use YAML-based configurations if you can!
In some cases, you may want to specify configuration sources as optional.
The classic use case is loading the configuration from one or multiple files that can optionally exist. There are three convenient methods for this:
- optional
File:
MyConfiguration myConfiguration = ConfigurationBuilder.newBuilder()
.optionalSource().fromFile(new File("some-path/application.yaml"))
.build(MyConfiguration.class);- optional file on classpath:
MyConfiguration myConfiguration = ConfigurationBuilder.newBuilder()
.optionalSource().fromFileOnClasspath("application.yaml")
.build(MyConfiguration.class);- optional file on path:
MyConfiguration myConfiguration = ConfigurationBuilder.newBuilder()
.optionalSource().fromFileOnPath("/Users/yacl4j/application.yaml"))
.build(MyConfiguration.class);Now suppose you have defined a custom configuration source and you want it to be optional.
You just need to:
- throw a
ConfigurationSourceNotAvailableExceptionin yourConfigurationSourceimplementation when appropriate, e.g.
public class MyConfigurationSource implements ConfigurationSource {
@Override
public JsonNode getConfiguration() {
if (isSourceAvailable()) {
// ...
} else {
throw new ConfigurationSourceNotAvailableException();
}
}
}- use the
optionalSourcemethod on theConfigurationBuilderwhich accepts aConfigurationSourceas parameter, e.g.
MyConfiguration myConfiguration = ConfigurationBuilder.newBuilder()
.optionalSource(new MyConfigurationSource())
.build(MyConfiguration.class);If, for some reason, you configuration source eagerly checks the availability of the source while it is being instantiated, you can:
- throw a
ConfigurationSourceNotAvailableExceptionin the constructor or factory of yourConfigurationSourceimplementation, e.g.
public class MyConfigurationSource implements ConfigurationSource {
public MyConfigurationSource() {
if (isSourceAvailable()) {
// ...
} else {
throw new ConfigurationSourceNotAvailableException();
}
}
}- use the
optionalSourcemethod on theConfigurationBuilderwhich accepts aSupplier<ConfigurationSource>as parameter, e.g.
MyConfiguration myConfiguration = ConfigurationBuilder.newBuilder()
.optionalSource(() -> new MyConfigurationSource())
.build(MyConfiguration.class);yacl4j is released under the Apache 2.0 license.