Contents

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 nullgetResourceAsStream() 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.