Properties Files: Reading and Writing .properties
The java.util.Properties class is a simple, persistent keyβvalue store that reads and writes .properties files: text files where each line has the form key=value. Properties files are used for application configuration, locale-specific messages, and build settings. This article covers the full Properties API: reading from files and the classpath, writing and storing, the XML variant, system properties, and how Properties compares to using a plain Map.
Contents
What is a .properties file?
A .properties=:#!\uXXXXsrc/main/resources
// 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
Reading with Properties.load()
PropertiesHashtableload(InputStream)load(Reader)Readerload()Properties
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");
load(InputStream)\uXXXXload(Reader)ReadergetProperty() with defaults
getProperty(key)nullgetProperty(key, defaultValue)nullget(key)HashtableObjectString
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());
}
Reading from the classpath
Properties files bundled inside a JAR do not have a filesystem path: they can only be accessed through the classpath using getResourceAsStream()new FileInputStream("app.properties")ClassLoader.getResourceAsStream("config/app.properties")getClass().getResourceAsStream("/config/app.properties")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
nullgetResourceAsStream()nullload()Writing and storing properties
store(OutputStream, comment)nullsetProperty(key, value)store(Writer, comment)
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");
}
XML properties format
Properties.loadFromXML()storeToXML()<entry key="...">
// 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
System.getProperties() and system properties
JVM system properties are set at startup with -Dkey=valueSystem.getProperty(key)System.getProperties()Propertiesjava.versionos.nameuser.homeSystem.setProperty()
// 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
-Djava -Dkey=valueProperties vs Map
PropertiesHashtable<Object, Object>put()get()ObjectgetProperty()Map<String, String>Properties.propertiesProperties
// 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>
PropertiesHashtable<Object, Object>put(key, value)getProperty()setProperty(String, String)getProperty(String)Map