Configuring Java Applications

Note: maven & Java 17 or higher are assumed.

Operating System Environment Variables

You can name your environment variables however you want, but they are typically in screaming snake case by convention (and in certain style guides).

Using export

#!/usr/bin/env bash

export SERVER_PORT=8080
java -jar app.jar

Note: Using the export keyword makes environment variables available for all child processes.

Using env

#!/usr/bin/env bash

env SERVER_PORT=8080 \ 
    APPLICATION_NAME=sample \ 
    FOO=BAR \
    java -jar app.jar

The benefit of using the env command is that it will set the environment only for the duration of the "java" command. This means that any other processes running in the same shell will not see these variables.

Accessing Environment Variables using Java

From the Java side of things, you can access environment variables using System.getenv() or getenv(java.lang.String)

public class EnvironmentVariablesExample {
    public static void main(String... ignore) {
        Map<String, String> environmentVariables = System.getenv();
        // Do something with environmentVariables
    }
}

Note: While it is trivial to read environment variables, there isn't a straightforward way to update or add environment variables in Java unless you spawn a subprocess

System Properties

Prefix Description Documentation Example
- Standard JVM properties supported by most JVM implementations. Standard options for Java -verbose
-D User-defined system properties. Hopefully you're using something like Spring's Configuration Metadata -Dserver.port
-X General purpose options that are specific to the Java HotSpot Virtual Machine. Extra Options for Java -Xcheck:jni
-XX Advanced runtime options, JIT options, Serviceability options, and garbage collection options. Advanced Options for Java -XX:+UnlockExperimentalVMOptions

Note: Refer to the Java command documentation for your version of Java and your JVM vendor documentation (e.g. Azul) for more information about what options are available.

Considering user-defined properties system properties, you'd supply them as follows:

java -Dserver.port=8080 -jar app.jar

Setting system properties via Environment Variables

Environment Variable Documentation Reference
JDK_JAVA_OPTIONS Using the JDK_JAVA_OPTIONS Launcher Environment Variable
JAVA_TOOL_OPTIONS JAVA_TOOL_OPTIONS
JAVA_OPTS Not a standard environment variable and won't take effect automatically. Despite that, you'll find it used all over the place e.g. Jenkins, various application servers like Tomcat, JBoss etc.

For example, instead of passing system properties directly, you can configure them via the JDK_JAVA_OPTIONS environment variable:

export JDK_JAVA_OPTIONS="-Dserver.port=value"
java -jar app.jar

Accessing System Properties using Java

From the Java side of things, system properties can be accessed via System.getProperties() or System.getProperty(java.lang.String):

public class SystemPropertiesExample {
    public static void main(String... ignore) {
        Properties systemProperties = System.getProperties();
        // Do something with systemProperties
    }
}

Command Line Arguments

Passed at runtime to your application e.g.

#!/usr/bin/env bash

java -jar app.jar "server.port=8080" "application.name=foo" "service.apiKey=bar"

Accessing System Properties using Java

From the Java side of things, the CLI arguments can be accessed from the main method:

public class CommandLineArgumentsExample {
    public static void main(String... commandLineArguments) {
        // Do something with commandLineArguments
    }
}

File Based Configuration

Configuration files can be:

  1. Bundled with the application as a classpath resource.
  2. Looked up from a well-known location e.g. in the user's home directory.
  3. Specified at runtime using CLI Args, System Properties, or Environment Variables.

Properties files are a standard way to define configuration for Java applications with built-in support.

Parsing other configuration formats

Including properties files, the jackson-dataformats-text libraries can be used to parse other formats such as XML, JSON, YAML/YML, INI/TOML, CSV etc.

Include the dependency for your desired format in your POM:

<dependencies>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml -->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-yaml</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-toml -->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-toml</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-csv -->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-csv</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-xml -->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-properties -->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-properties</artifactId>
        </dependency>   
</dependencies>

Assuming that you have the following Java class modelling your configuration:

class ApplicationConfiguration {
    public String environment;
    public Metadata metadata;
    public DatabaseConfiguration databaseConfiguration;

    record Metadata(String name, String version) {
    }

    record DatabaseConfiguration(String host,
                                 String port,
                                 String database,
                                 String username,
                                 String password,
                                 String jdbcUrl) {
    }

}

And that the following YAML file represents your configuration:

environment: "learning"
metadata:
  name: "My Application"
  version: "1.0.0"
databaseConfiguration:
  host: "localhost"
  port: "3306"
  database: "my_database"
  username: "service-account"
  password: "something-hopefully-strong"
  jdbcUrl: "jdbc:mysql://localhost:3306/my_database"

Then you can read it from the file system as follows:

import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;

class ReadFromFileSystem {


    public static void main(String... commandLineArguments) throws Exception {
        final var configurationFilePath = Paths.get(System.getProperty("user.home"), "config.yaml");
        // or: ObjectMapper, JavaPropsMapper, TomlMapper, XmlMapper, CsvMapper
        final var mapper = new YAMLMapper();
        try (var inputStream = Files.newInputStream(configurationFilePath)) {
            Objects.requireNonNull(inputStream, configurationFilePath + " not found!");
            var configuration = mapper.readValue(inputStream, ApplicationConfiguration.class);
            // do something with configuration
        }
    }

}

Alternatively, if it is available on the classpath, you can read it as follows:

import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;

import java.util.Objects;

class ReadFromClasspath {


    public static void main(String... commandLineArguments) throws Exception {
        final var classpathResource = "config.yaml";
        // or: ObjectMapper, JavaPropsMapper, TomlMapper, XmlMapper, CsvMapper
        final var mapper = new YAMLMapper();
        try (var inputStream = ReadFromClasspath.class.getClassLoader().getResourceAsStream(classpathResource)) {
            Objects.requireNonNull(inputStream, classpathResource + " not found!");
            var configuration = mapper.readValue(inputStream, ApplicationConfiguration.class);
            // do something with configuration
        }
    }
}

References / Additional Reading