Almost all applications need to run in multiple environments. If nothing else, they are tested on a developer’s workstation before deployment to a production box. There can be QA and UAT environments in between, and there might even be more than one production target. At runtime, applications usually need environment-specific info such as which database to use, credentials, etc.
Large-scale apps normally manage this with a dependency-injection framework like Spring. The typical “cloud” host approach is to use environment variables. Both of these can be used in combination. However, what about small, simple apps? Scenarios where you don’t want to add tens of megs of unnecessary frameworks, yet there is more information than you care to sick in environment variables?
One approach is to use traditional properties files, parsed and loaded by the java.util.Properties class. You would maintain a separate properties file for each target environment, and rely on your build system to bundle the correct version with each distribution.
For an even easier approach, take a minute and go search the hard drive of a Windows box for files having the “*.ini” extension.
These INI files have long been forgotten, as Windows configuration moved into the registry almost twenty years ago. However, they were once the standard means for managing DOS and Windows config. Some of them are still there today, such as this C:\WINDOWS\SYSTEM.INI:
; for 16-bit app support [386Enh] woafont=dosapp.fon EGA80WOA.FON=EGA80WOA.FON EGA40WOA.FON=EGA40WOA.FON CGA80WOA.FON=CGA80WOA.FON CGA40WOA.FON=CGA40WOA.FON [drivers] wave=mmdrv.dll timer=timer.drv [mci]
This looks like standard java.util.Properties file, with information stored in “name”=”value” pairs. However, notice the bracketed lines splitting the properties into groups. These groups are called “sections”, or sometimes “stanzas”, and are a really useful means for logically separating config values.
Imagine using the sections to configure a database connection and web service endpoint for multiple environments:
[dev] JDBC_URL = jdbc:h2:mem:mem_test;MODE=Oracle JDBC_USERNAME = JDBC_PASSWORD = SERVICE_ENDPOINT = http://localhost:8080/Central/api/AppService [qa] JDBC_URL = jdbc:oracle:thin:@qa-oracle:1521:qa JDBC_USERNAME = qauser JDBC_PASSWORD = qapass SERVICE_ENDPOINT = http://qa-services/Central/api/AppService [prod] JDBC_URL = jdbc:oracle:thin:@prod-oracle:1521:prod JDBC_USERNAME = scott JDBC_PASSWORD = tiger SERVICE_ENDPOINT = http://prod-services/Central/api/AppService
(Perhaps you would use an environment variable for the database password, rather than sticking it in a plaintext file, but you see the basic idea.)
All database and web service information is in one centralized place, and it’s easy to see and manage differences between environments. The dev environment uses an in-memory H2 database for testing, and runs the web service on localhost. The qa and prod environments use Oracle, and the web services they use are on dedicated machines.
To easily use this information in Java (or Scala, Groovy, etc), you can use the open-source [ini4j] library. [ini4j] is tiny, and has no additional dependencies. You can download it directly from SourceForge, or from Maven Central as a dependency in your Maven project’s POM:
... <dependency> <groupId>org.ini4j</groupId> <artifactId>ini4j</artifactId> <version>0.5.2</version> </dependency> ...
The [ini4j] website has very good documentation and tutorials, but using the library in your applications is really as simple as knowing two classes:
... Ini ini = new Ini(); ini.load(this.getClass().getResourceAsStream("/config.ini")); Section section = ini.get(ENVIRONMENT); // "dev", "prod", etc. String jdbcUrl = section.get("JDBC_URL"); if(jdbcUrl == null) throw new Exception("Missing required config property \"JDBC_URL\""); ...
The Ini class is used to load your INI file, and the easiest approach is through a resource stream like any normal properties file. Once a file is loaded, use the Section class to grab a particular section by name.
You might have some logic that chooses the right section from the hostname on which the application is currently running. Or you might just set an environment variable (better to set one or two, rather than having to set many of them).
I won’t argue that this approach is the absolute best in all use cases. However, if your Java application (or Scala/Groovy script) is too small for a dependency-injection framework, yet has more config than you care to push into environment variables, then INI files are an old-school gem that really hit a sweet spot in between.