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); // #7
In 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: yacl4j
The above configuration becomes:
greeting: Hello yacl4j
name: yacl4j
Let's consider an example:
greeting: Hello ${person/name}
person:
name: yacl4j
The above configuration becomes:
greeting: Hello yacl4j
person:
name: yacl4j
Let's consider an example:
greeting: Hello ${persons/0}
persons:
- yacl4j
The above configuration becomes:
greeting: Hello yacl4j
persons:
- yacl4j
Let's consider an example:
object:
property: value
myObject: ${object}
The above configuration becomes:
object:
property: value
myObject:
object:
property: value
Let's consider an example:
array:
- value
myArray: ${array}
The above configuration becomes:
array:
- value
myArray:
array:
- value
For 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-b03
The 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-b03
Mmmmm...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-b03
Hey, 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-b03
Better, 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
ConfigurationSourceNotAvailableException
in yourConfigurationSource
implementation when appropriate, e.g.
public class MyConfigurationSource implements ConfigurationSource {
@Override
public JsonNode getConfiguration() {
if (isSourceAvailable()) {
// ...
} else {
throw new ConfigurationSourceNotAvailableException();
}
}
}
- use the
optionalSource
method on theConfigurationBuilder
which accepts aConfigurationSource
as 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
ConfigurationSourceNotAvailableException
in the constructor or factory of yourConfigurationSource
implementation, e.g.
public class MyConfigurationSource implements ConfigurationSource {
public MyConfigurationSource() {
if (isSourceAvailable()) {
// ...
} else {
throw new ConfigurationSourceNotAvailableException();
}
}
}
- use the
optionalSource
method on theConfigurationBuilder
which 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.