Contents
- What is a .properties file?
- Reading with Properties.load()
- getProperty() with defaults
- Reading from the classpath
- Writing and storing properties
- XML properties format
- System.getProperties() and system properties
- Properties vs Map
A .properties file is a plain text configuration format where each line contains a key and value separated by =, :, or whitespace. Lines starting with # or ! are comments. A backslash at the end of a line continues the value on the next line, and Unicode characters can be embedded using \uXXXX escape sequences. Keys are case-sensitive. Values are always strings — type conversion (to int, boolean, etc.) is the responsibility of the calling code. In Maven and Gradle projects, properties files are placed in src/main/resources and copied to the classpath root during the build.
// A .properties file is a plain text file of key=value pairs.
// Format rules:
// - Keys and values are separated by = or :
// - Leading and trailing whitespace around the separator is trimmed
// - Lines starting with # or ! are comments
// - Long values can be split across lines with a backslash continuation
// - Unicode escapes (\uXXXX) are supported
// - Keys may contain any character except =, :, or whitespace (unescaped)
// Example file: app.properties
// # Database settings
// db.host = localhost
// db.port = 5432
// db.name = myapp
// db.user = admin
// db.password = s3cr3t!
//
// # Feature flags
// feature.dark-mode = true
// feature.beta = false
//
// # Multi-line value
// welcome.message = Hello, welcome to \
// MyApp version 2.0!
//
// # Unicode
// greeting.japanese = \u3053\u3093\u306b\u3061\u306f
// Keys are case-sensitive:
// "DB.HOST" and "db.host" are different keys
// Colons and equals are both valid separators:
// server.port: 8080
// server.port = 8080 ← both are equivalent
// Values are always Strings — type conversion is the caller's responsibility
Properties extends Hashtable and stores all keys and values as strings. load(InputStream) parses the file using ISO-8859-1 encoding; use load(Reader) with an explicit UTF-8 Reader to correctly handle non-ASCII characters without Unicode escapes. The stream or reader should be closed after loading — use try-with-resources. Calling load() on a Properties object that already has entries merges the new keys in, overwriting any duplicates.
import java.util.Properties;
import java.io.*;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
Properties props = new Properties();
// Load from a File — load(InputStream) uses ISO-8859-1 (legacy)
try (InputStream is = new FileInputStream("app.properties")) {
props.load(is); // ISO-8859-1 decoding — fine for ASCII
}
// Load with explicit UTF-8 — use load(Reader) for Unicode support
// This is the PREFERRED approach for modern code
try (Reader reader = Files.newBufferedReader(
Path.of("app.properties"), StandardCharsets.UTF_8)) {
props.load(reader);
}
// Print all loaded properties
props.forEach((key, value) -> System.out.println(key + " = " + value));
// List to a PrintStream (useful for debugging)
props.list(System.out);
// Load preserves insertion order only with LinkedProperties workaround;
// standard Properties iteration order is not guaranteed
// Reload / merge — load() adds to existing entries, overwriting duplicates
Properties defaults = new Properties();
defaults.setProperty("timeout", "30");
defaults.setProperty("retry", "3");
Properties override = new Properties(defaults); // defaults as fallback
try (Reader r = Files.newBufferedReader(Path.of("app.properties"), StandardCharsets.UTF_8)) {
override.load(r); // override-specific keys take priority
}
// size — number of entries
System.out.println("Loaded " + props.size() + " properties");
The load(InputStream) overload uses ISO-8859-1 encoding, not UTF-8. Non-ASCII characters (accented letters, CJK, emoji) must be written as \uXXXX Unicode escapes in the file if you use the InputStream overload. Use load(Reader) with an explicit UTF-8 Reader to support Unicode characters directly in the file.
getProperty(key) returns null when the key is absent. The two-argument form getProperty(key, defaultValue) returns the default instead of null, which is almost always the safer choice — it avoids null checks and makes the fallback value explicit at the call site. Avoid the inherited get(key) method from Hashtable: it returns Object, bypasses the default lookup chain, and skips keys whose values are not String instances.
Properties props = new Properties();
props.setProperty("db.host", "localhost");
props.setProperty("db.port", "5432");
props.setProperty("db.name", "myapp");
// getProperty(key) — returns the value or null if key not found
String host = props.getProperty("db.host"); // "localhost"
String user = props.getProperty("db.user"); // null
// getProperty(key, defaultValue) — returns default if key not found
String dbUser = props.getProperty("db.user", "root"); // "root"
int port = Integer.parseInt(props.getProperty("db.port", "3306")); // 5432
// containsKey — check before getting (or just use the default overload)
if (props.containsKey("feature.beta")) {
boolean betaEnabled = Boolean.parseBoolean(props.getProperty("feature.beta"));
System.out.println("Beta: " + betaEnabled);
}
// getOrDefault — inherited from Hashtable (returns Object, requires cast)
String pass = (String) props.getOrDefault("db.password", "changeme");
// Iterating all entries
for (String key : props.stringPropertyNames()) { // returns Set<String>
System.out.println(key + " = " + props.getProperty(key));
}
// Chained fallback: environment variable > properties file > hardcoded default
String dbHost = System.getenv("DB_HOST"); // env var first
if (dbHost == null) dbHost = props.getProperty("db.host"); // then file
if (dbHost == null) dbHost = "localhost"; // then hardcoded
System.out.println("Using host: " + dbHost);
// Type-safe getters using helper methods
static int getInt(Properties p, String key, int def) {
String val = p.getProperty(key);
if (val == null) return def;
try { return Integer.parseInt(val.strip()); }
catch (NumberFormatException e) { return def; }
}
static boolean getBool(Properties p, String key, boolean def) {
String val = p.getProperty(key);
return val == null ? def : Boolean.parseBoolean(val.strip());
}
Properties files bundled inside a JAR do not have a filesystem path — they can only be accessed through the classpath using getResourceAsStream(). Do not use new FileInputStream("app.properties") in production code; it relies on the current working directory and breaks when the application runs from a JAR. Use ClassLoader.getResourceAsStream("config/app.properties") (always root-relative, no leading slash) or getClass().getResourceAsStream("/config/app.properties") (leading slash for root-relative). Always null-check the returned stream before passing it to Properties.load().
// Properties files in src/main/resources are packaged into the JAR
// and accessed via the classpath — NOT via a file path
// Pattern 1: getClass().getResourceAsStream() — relative to the class package
Properties props = new Properties();
try (InputStream is = getClass().getResourceAsStream("/config/app.properties")) {
if (is == null) throw new IllegalStateException("app.properties not found on classpath");
props.load(new InputStreamReader(is, StandardCharsets.UTF_8));
}
// Pattern 2: ClassLoader.getResourceAsStream() — always absolute (no leading /)
try (InputStream is = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream("config/app.properties")) {
if (is == null) throw new IllegalStateException("Resource not found");
props.load(new InputStreamReader(is, StandardCharsets.UTF_8));
}
// Pattern 3: using the class's own ClassLoader (works in modular apps)
try (InputStream is = MyConfig.class.getClassLoader()
.getResourceAsStream("config/app.properties")) {
if (is == null) throw new IllegalStateException("Resource not found");
props.load(new InputStreamReader(is, StandardCharsets.UTF_8));
}
// Typical Maven/Gradle project layout:
// src/main/resources/
// config/
// app.properties → classpath: "config/app.properties"
// application.properties → classpath: "application.properties"
// Merging defaults + overrides from classpath
Properties defaults = new Properties();
try (InputStream is = MyApp.class.getResourceAsStream("/defaults.properties")) {
defaults.load(new InputStreamReader(is, StandardCharsets.UTF_8));
}
Properties config = new Properties(defaults); // use defaults as fallback
try (InputStream is = MyApp.class.getResourceAsStream("/app.properties")) {
if (is != null) {
config.load(new InputStreamReader(is, StandardCharsets.UTF_8));
}
}
// Keys in app.properties override defaults.properties; missing keys fall back
System.out.println(config.getProperty("timeout")); // from whichever file has it
When loading a classpath resource, always check for null — getResourceAsStream() returns null (not an exception) if the resource is not found. A null InputStream causes a NullPointerException later when load() is called, which gives a confusing error message. Fail fast with a clear message instead.
store(OutputStream, comment) writes the properties back to a stream in the standard key=value format, prefixed with a comment header and an auto-generated timestamp. Pass null as the comment to suppress the header line entirely. setProperty(key, value) adds a new entry or updates an existing one. Use store(Writer, comment) with an explicit UTF-8 writer if the values contain non-ASCII characters. Note that the output order of keys is not guaranteed — entries may appear in any order.
import java.util.Properties;
import java.io.*;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
Properties props = new Properties();
props.setProperty("app.name", "MyApp");
props.setProperty("app.version", "2.0.0");
props.setProperty("app.debug", "false");
props.setProperty("app.maxConnections", "10");
// store(OutputStream, comment) — writes ISO-8859-1, with a header comment
try (OutputStream os = new FileOutputStream("output.properties")) {
props.store(os, "Application configuration — auto-generated");
}
// Output (order not guaranteed):
// #Application configuration — auto-generated
// #Mon Jan 01 12:00:00 UTC 2025
// app.name=MyApp
// app.version=2.0.0
// ...
// store(Writer, comment) — use with explicit UTF-8 writer for Unicode support
try (Writer writer = Files.newBufferedWriter(
Path.of("output.properties"), StandardCharsets.UTF_8)) {
props.store(writer, "Generated config");
}
// Add or update a key
props.setProperty("app.debug", "true"); // overwrites existing
props.setProperty("app.logLevel", "INFO"); // new key
// Remove a key
props.remove("app.debug");
// Update if absent (Java 8+) — inherited from Hashtable via Map
props.putIfAbsent("app.timeout", "30");
// Check and get in one step
String version = props.computeIfAbsent("app.version", k -> "1.0.0").toString();
// Merge from another Properties object
Properties extra = new Properties();
extra.setProperty("feature.x", "enabled");
extra.forEach((k, v) -> props.setProperty((String) k, (String) v));
// Store with sorted keys — default store() writes in an unpredictable order
// Workaround: use a TreeMap
Properties sorted = new Properties() {
@Override
public Set<Map.Entry<Object, Object>> entrySet() {
return new TreeMap<>(super.entrySet().stream()
.collect(java.util.stream.Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue)))
.entrySet();
}
};
sorted.putAll(props);
try (Writer w = Files.newBufferedWriter(Path.of("sorted.properties"), StandardCharsets.UTF_8)) {
sorted.store(w, "Sorted config");
}
Properties.loadFromXML() and storeToXML() use a structured XML format with <entry key="..."> elements instead of the flat key=value text format. Because the XML format enforces UTF-8 encoding, it is a better choice than the plain text format when property values contain non-ASCII characters that you want to store directly without Unicode escapes. This format is also useful when the properties file will be consumed or validated by XML-aware tools.
// Properties can also be stored as XML (ISO 10646 encoding)
// Format: loadFromXML / storeToXML
// Example XML properties file:
// <?xml version="1.0" encoding="UTF-8"?>
// <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
// <properties>
// <comment>Application configuration</comment>
// <entry key="db.host">localhost</entry>
// <entry key="db.port">5432</entry>
// <entry key="db.name">myapp</entry>
// </properties>
import java.util.Properties;
import java.io.*;
import java.nio.file.*;
// Load from XML
Properties xmlProps = new Properties();
try (InputStream is = new FileInputStream("config.xml")) {
xmlProps.loadFromXML(is);
}
System.out.println(xmlProps.getProperty("db.host")); // localhost
// Load from XML on classpath
try (InputStream is = getClass().getResourceAsStream("/config/app-config.xml")) {
xmlProps.loadFromXML(is);
}
// Store to XML
Properties toSave = new Properties();
toSave.setProperty("server.host", "example.com");
toSave.setProperty("server.port", "443");
toSave.setProperty("server.ssl", "true");
try (OutputStream os = new FileOutputStream("server.xml")) {
toSave.storeToXML(os, "Server configuration", "UTF-8");
}
// Generates well-formed XML with UTF-8 encoding — better for Unicode than .properties
// When to use XML properties vs .properties:
// .properties — simpler format, human-editable, universally understood
// XML — better Unicode support without escaping, schema validation possible,
// useful when the file is also consumed by non-Java tools
JVM system properties are set at startup with -Dkey=value flags and are accessible at runtime via System.getProperty(key). They are the standard mechanism for passing environment-specific configuration (database URLs, log levels, feature flags) to a Java application without modifying bundled resource files. System.getProperties() returns all system properties as a Properties object, including JVM-provided values like java.version, os.name, and user.home. Avoid calling System.setProperty() in library code — it modifies global JVM state and affects all threads and classes loaded in the same JVM.
// System properties — set by the JVM and overridable via -D on the command line
// System.getProperty(key) — get a single property
// System.getProperties() — get all as a Properties object
// Common built-in system properties
System.out.println(System.getProperty("java.version")); // e.g. 21.0.2
System.out.println(System.getProperty("java.vendor")); // e.g. Eclipse Adoptium
System.out.println(System.getProperty("java.home")); // JDK/JRE install dir
System.out.println(System.getProperty("os.name")); // Windows 11 / Linux / Mac OS X
System.out.println(System.getProperty("os.arch")); // amd64 / aarch64
System.out.println(System.getProperty("user.name")); // login name
System.out.println(System.getProperty("user.home")); // home directory
System.out.println(System.getProperty("user.dir")); // current working directory
System.out.println(System.getProperty("file.separator")); // / or \
System.out.println(System.getProperty("line.separator")); // \n or \r\n
System.out.println(System.getProperty("path.separator")); // : or ;
System.out.println(System.getProperty("file.encoding")); // UTF-8
// Get with default
String logDir = System.getProperty("app.logDir", "/var/log/myapp");
// Set a property programmatically
System.setProperty("app.env", "staging");
System.out.println(System.getProperty("app.env")); // staging
// Set via command line: java -Dapp.env=production -jar myapp.jar
// Access all properties as a Properties object
Properties sysProps = System.getProperties();
sysProps.stringPropertyNames().stream()
.sorted()
.forEach(k -> System.out.println(k + " = " + sysProps.getProperty(k)));
// Merging system properties with app properties (system overrides app)
Properties appProps = new Properties();
try (Reader r = Files.newBufferedReader(Path.of("app.properties"), StandardCharsets.UTF_8)) {
appProps.load(r);
}
// Allow -D flags to override any app property
appProps.putAll(System.getProperties()); // system properties win
// Best practice: don't call System.setProperty() in library code —
// it modifies the global JVM state and affects all threads
Use -D flags (java -Dkey=value) to override configuration at startup without changing the properties file. This is the standard way to pass environment-specific settings (e.g., database URLs, log levels) to a Java application.
Properties is a legacy class that extends Hashtable<Object, Object>, which means the inherited put() and get() methods accept any Object, not just strings — a source of subtle bugs when non-string values are stored silently and then silently ignored by getProperty(). For new code that is not required to interoperate with file-based properties, a Map<String, String> is simpler, type-safe, and stream-friendly. Keep using Properties when loading .properties files, accessing system properties, or passing configuration to APIs that expect a Properties object.
// Properties extends Hashtable<Object, Object> — a legacy design
// This means it inherits methods like put(Object, Object) that bypass type safety
Properties props = new Properties();
// SAFE — use the String-typed API
props.setProperty("key", "value"); // good
String v = props.getProperty("key"); // good
// UNSAFE — inherited Hashtable methods accept Object, not just String
props.put("key", 42); // stores Integer — NOT a String!
props.put(100, "value"); // stores Integer key — breaks iteration!
// getProperty() skips non-String values — so this returns null
props.put("count", 5);
System.out.println(props.getProperty("count")); // null — because value is Integer, not String
System.out.println(props.get("count")); // 5 — using Map.get() works
// Always use setProperty/getProperty, never put/get for Properties
// Enforcing this with a wrapper or using a Map<String,String> instead:
Map<String, String> config = new LinkedHashMap<>();
config.put("db.host", "localhost");
config.put("db.port", "5432");
// Convert Map to Properties for legacy APIs
Properties fromMap = new Properties();
fromMap.putAll(config); // safe here because all keys/values are String
// Convert Properties to Map<String,String> (only String keys/values)
Map<String, String> toMap = props.stringPropertyNames().stream()
.collect(Collectors.toMap(k -> k, props::getProperty));
// When to use Properties vs Map:
// Properties — reading .properties files, system properties, ResourceBundle integration
// Map — new code where you control the data source; type-safe; stream-friendly
// Properties is thread-safe (Hashtable is synchronized) but Map is not
// For concurrent use of a Map, prefer ConcurrentHashMap<String, String>
Properties extends Hashtable<Object, Object>, so the inherited put(key, value) method accepts non-String values. These values are silently ignored by getProperty(), causing subtle bugs. Always use setProperty(String, String) and getProperty(String) — never the raw Map methods.