DEV Community

Carlos Chacin ☕👽
Carlos Chacin ☕👽

Posted on • Originally published at cchacin.github.io on

Java 8 Type-Safe Configuration with default methods

There is a common requirement in most of the Java/JavaEE projects related to how to differentiate environment configurations like development, test, QA, production, etc. for that reason there are a lot of frameworks or libraries to resolve that:

Even there is a Java Specification Request (JSR) Proposal:

How those libraries resolve the problem

Most of those libraries require to have an injection point(s) for the configurations and also the source of the configuration(s), something like this:

dev.properties:

host=localhost port=5432 schema=public 
Enter fullscreen mode Exit fullscreen mode

prod.properties:

host=production.com port=5432 schema=public 
Enter fullscreen mode Exit fullscreen mode

ConfigurationResolver.java:

class ConfigurationResolver implements SomeLibraryInterface { @Override public String resolvePropertyFilename() { // probably do some trick to load a global/base configuration // if is not provided for the library return String.format("%s.properties", System.getProperty("MY_ENV", "dev")); } } 
Enter fullscreen mode Exit fullscreen mode

MyType.java:

class MyType { @Configuration(key = "host") String host; @Configuration(key = "port") int port; @Configuration(key = "schema", defaultValue = "public") String schema; } 
Enter fullscreen mode Exit fullscreen mode

Notice that all the granularity is only needed because of lack of support for custom types

Besides that, with the new Java 8 capabilities, specifically with default methods and some utility classes are easy to get this behavior.

With Java 8 default methods

DefaultConfiguration.java:

interface DefaultConfiguration { default DataSource datasource { // create the DS with host, port and schema return new MyDataSource("localhost", 5432, "public"); } } 
Enter fullscreen mode Exit fullscreen mode

DevConfiguration.java:

class DevConfiguration implements DefaultConfiguration { // No need to override methods if they are the same } 
Enter fullscreen mode Exit fullscreen mode

ProdConfiguration:

class ProdConfiguration implements DefaultConfiguration { default DataSource datasource { return new MyDataSource("production.com", 5432, "public"); } } 
Enter fullscreen mode Exit fullscreen mode

ConfigurationFactory.java:

abstract class ConfigurationFactory { private static final Map<String, DefaultConfiguration> configurations = new HashMap<>(); static { configurations.put("development", new DevConfiguration()); configurations.put("production", new ProdConfiguration()); } public static DefaultConfiguration configuration() { return configurations.getOrDefault(System.getProperty("MY_ENV"), new DevConfiguration()); } } 
Enter fullscreen mode Exit fullscreen mode

Notice that you can use an enumeration instead of a map as a configuration factory.

Pros

  • Typesafe configurations
  • Support for all kinds of types not only String, Date or primitives. you can specify or example DataSource, TimeUnits, JedisPool, etc.
  • No conversion/mapping from strings to types
  • No more .properties, .xml or .yml files
  • Possibility to use other sources to fill the properties like properties files, database, rest API's, etc.
  • No library/framework/dependency required in your app

Cons

  • Changes in the configuration require to compile/deploy (this shouldn’t be a problem in the 99% of the cases)

source code

Top comments (0)