/envy

Super simple configuration for Java.

Primary LanguageJavaApache License 2.0Apache-2.0

Envy

Super simple configuration for Java.

Envy provides easy, uniform access to environment variables and system properties, which helps you build twelve-factor apps.

Say you need to access the environment variables API_KEY and API_SECRET in your program. First, you define an interface with getter methods named after the parameters you want to bring in:

interface MyConfig {
    String getApiKey();
    String getApiSecret();
}

Envy can then instantiate your configuration interface like this:

import com.statemachinesystems.envy.Envy;
...
MyConfig config = Envy.configure(MyConfig.class);

Now, calling config.getApiKey() will return the value of the API_KEY environment variable, and config.getApiSecret() will return the value of API_SECRET.

This also works for JVM system properties, using a lower-case dotted naming convention, which would be api.key and api.secret in the above example. When an environment variable and a system property are both defined with equivalent names, the system property takes precedence.

Also, you don't have to use bean-style method names - the following version would work in exactly the same way:

  interface MyConfig {
      String apiKey();
      String apiSecret();
  }

Getting started

Envy's in the Maven Central repo, so just add the latest version as a dependency in your Maven/SBT/Gradle/whatever build.

Default values

Envy treats all parameters as mandatory, but you can provide a default value using the @Default annotation:

import com.statemachinesystems.envy.Default;

interface ServerConfig {
    @Default("80")
    int getHttpPort();
}

Optional values

Sometimes a parameter type has no meaningful default value, or you need to test for its absence. Envy supports Java 8's Optional, Scala's Option and Guava's Optional types.

interface FooConfig {
    Optional<URL> getUrl();
}

You can force Envy to allow null values using the @Nullable annotation:

import com.statemachinesystems.envy.Nullable;

interface FooConfig {
    @Nullable
    URL nullableUrl();
}

Sensitive values

By default, Envy provides a toString() method that includes all configured values. To mask out sensitive values such as passwords, use the @Sensitive annotation:

import com.statemachinesystems.envy.Sensitive;

interface Credentials {
    String username();
    @Sensitive String password();
}

Custom naming

Long and/or awkward names can be overridden using the @Name annotation:

import com.statemachinesystems.envy.Name;

interface BarConfig {
    @Name("com.foo.extremely.long.property.name.for.thing")
    String getThing();
}

To apply a prefix to all names in a configuration interface, use the @Prefix annotation:

import com.statemachinesystems.envy.Prefix;

@Prefix("baz.config")
interface BazConfig {
    /**
     * Configured by BAZ_CONFIG_HTTP_PORT
     */
    int getHttpPort();
}

Inheritance

Interface inheritance is supported, so you can factor out common parameters in more complex configurations.

Annotations on methods (@Name, @Nullable and @Default) are inherited, but can be overridden (or removed entirely) by redeclaring the method in a sub-interface. @Prefix annotations are not inherited.

Nesting

Configuration interfaces can be nested, which allows reuse of repeated structures. Here's an example using both inheritance and nesting:

interface Credentials {
    String username();
    @Sensitive String password();
}

interface ConnectionConfig extends Credentials {
    java.net.InetSocketAddress address();
}

interface AppConfig {
    /**
     * Configured by DATABASE_ADDRESS, DATABASE_USERNAME and DATABASE_PASSWORD
     */
    ConnectionConfig database();

    /**
     * Configured by MESSAGE_BROKER_ADDRESS, MESSAGE_BROKER_USERNAME and MESSAGE_BROKER_PASSWORD
     */
    ConnectionConfig messageBroker();
}

Supported data types

Envy will do the following type conversions for you:

  • Strings (no conversion needed)
  • Numbers (int/Integer, long/Long, byte/Byte, short/Short, float/Float, double/Double, java.math.BigDecimal, java.math.BigInteger)
  • Booleans (true/false, yes/no, y/n, on/off)
  • Characters (char/Character)
  • Enums
  • Arrays, comma-separated
  • Anything with a constructor that takes a single String argument
  • java.io.File
  • java.lang.Class
  • java.net.URL, java.net.URI
  • java.net.InetAddress, Inet4Address, Inet6Address, InetSocketAddress
  • java.util.regex.Pattern
  • java.util.UUID
  • java.time.Duration e.g. "100ms", "30 seconds", "1000" (defaults to millis)
  • java.time.Period e.g. "1d", "2 weeks", "3 months", "10" (defaults to days)

Custom data types

To parse a custom type, implement the ValueParser interface:

import com.statemachinesystems.envy.ValueParser;

public class MyCustomTypeParser implements ValueParser<MyCustomType> {
    @Override
    public MyCustomType parseValue(String value) {
        ...
    }

    @Override
    public Class<MyCustomType> getValueClass() {
        return MyCustomType.class;
    }
}

Then, when instantiating your configuration, pass along an instance of your parser like this:

MyConfig config = Envy.configure(MyConfig.class, new MyCustomTypeParser());

© 2014-2018 State Machine Systems Ltd. Apache Licence, Version 2.0