W3docs

Java Properties-Klasse

String-Schlüssel-Wert-Paare in Java mit der Properties-Klasse laden und speichern, einschließlich .properties-Dateien.

Properties ist der JDK-Container für String-zu-String-Konfigurationen — Anwendungseinstellungen, Umgebungsüberschreibungen, lokalisierte Meldungen, JDBC-Verbindungsparameter. Er erweitert Hashtable<Object, Object> (eine historische Entscheidung, die wir heute bereuen, aber damit leben) und fügt drei Dinge hinzu: ein .properties-Dateiformat mit Reader und Writer, einen XML-Reader und -Writer sowie das Konzept eines Default-Properties-Objekts, das abgefragt wird, wenn ein Schlüssel lokal nicht gefunden wird.

System.getProperties() gibt ein Properties-Objekt zurück. Jeder Aufruf von System.getProperty("user.home") geht durch es. Sobald man die Klasse kennt, sieht man sie überall.

Der Vertrag: Strings auf beiden Seiten

Obwohl die Klasse die Methode put(Object, Object) von Hashtable erbt, ist das einzig sichere API das String-typisierte Paar:

Properties config = new Properties();
config.setProperty("server.port", "8080");
config.setProperty("server.host", "localhost");
String port = config.getProperty("server.port");          // "8080"
String log  = config.getProperty("log.level", "INFO");    // default fallback

Wenn man setProperty umgeht und put("server.port", 8080) mit einem Integer aufruft, landet der Eintrag zwar in der Tabelle, man hat aber eine Falle gelegt: stringPropertyNames() filtert ihn still heraus, und die Dateischreibmethoden (store, storeToXML) werfen eine ClassCastException, sobald sie versuchen, diesen Integer in String umzuwandeln. Behandle Properties wie Properties<String, String>, auch wenn die Generics das nicht ausdrücken.

Das .properties-Dateiformat

Klartext, zeilenorientiert, key=value. Leerzeichen um = sind erlaubt. Zeilen, die mit # oder ! beginnen, sind Kommentare. Ein abschließender Backslash setzt den Wert in der nächsten Zeile fort. Unicode-Escapes (\uXXXX) werden unterstützt, aber seit Java 9 liest die Überladung load(Reader) nativ UTF-8, sodass man sie selten braucht.

# server.properties — last edited 2026-05-12
server.host = localhost
server.port = 8080
server.path = /api/v1
greeting    = Welcome, \
              user!

load und store verwenden dieses Format. loadFromXML und storeToXML verwenden das entsprechende XML-Format, das durch properties.dtd definiert wird — es existiert, ist gelegentlich hilfreich, wird aber fast nie der Textform vorgezogen.

Laden und Speichern

Properties config = new Properties();
try (var in = Files.newBufferedReader(Path.of("server.properties"))) {
  config.load(in);              // UTF-8 text
}

config.setProperty("server.port", "9090");

try (var out = Files.newBufferedWriter(Path.of("server.properties"))) {
  config.store(out, "edited by setup script");   // comment becomes the first line
}

Die Methode store schreibt nach dem Benutzerkommentar einen Zeitstempel-Kommentar, sortiert nichts (Einträge erscheinen in der Iterationsreihenfolge von Hashtable) und maskiert Sonderzeichen (=, :, #, führende Leerzeichen) für das Round-Tripping. Die Ausgabe ist zwischen JVMs portierbar.

Für Ressourcen, die mit der Anwendung gebündelt sind, wird statt aus dem Dateisystem aus dem Classpath geladen:

try (var in = MyApp.class.getResourceAsStream("/app.properties")) {
  config.load(in);              // load(InputStream) defaults to ISO-8859-1
}

load(InputStream) ist die historische Überladung und verwendet ISO-8859-1 (Latin-1) mit \u-Escapes. load(Reader) verwendet den Zeichensatz, mit dem der Reader geöffnet wurde. Bevorzuge die Reader-Form, wenn du die Kodierung kontrollierst.

Default-Properties: geschichtete Konfiguration

Das zweiargumentige getProperty(key, default) gibt einen Fallback zurück, wenn der Schlüssel fehlt. Der Konstruktor Properties(Properties defaults) macht dasselbe, aber auf Objektebene — das zweite Properties-Objekt wird abgefragt, wenn das erste den Schlüssel nicht enthält:

Properties base = new Properties();
base.setProperty("server.port", "8080");
base.setProperty("log.level",   "INFO");

Properties override = new Properties(base);   // base is the defaults
override.setProperty("log.level", "DEBUG");   // override wins

override.getProperty("server.port");          // "8080"  (from base)
override.getProperty("log.level");            // "DEBUG" (from override)

Das ist das Standardmuster für "Mit der App gelieferte Standardwerte, die der Benutzer pro Umgebung überschreiben kann." Zwei Ebenen sind der häufige Fall; man kann auch mehr verketten.

System-Properties und -D-Flags

Die JVM hat eine globale Properties-Instanz, die über System.getProperties() und System.getProperty(key) zugänglich ist. Standardschlüssel sind java.version, user.home, user.dir, os.name, file.separator und line.separator. Das -Dkey=value-Flag in der JVM-Befehlszeile fügt Einträge hinzu, bevor main ausgeführt wird:

java -Dserver.port=9090 -Dlog.level=DEBUG -jar app.jar
String port = System.getProperty("server.port", "8080");

Das ist die einfachste "Kommandozeilen-Konfiguration", die man einem Java-Programm geben kann. Bei größeren Konfigurationen ist die Konvention eine .properties-Datei, die mit der App ausgeliefert wird und beim Start mit System-Properties zusammengeführt wird (die als Überschreibungen fungieren).

Was Properties nicht ist

  • Keine Map mit beliebigen Typen. Nur Strings. Integer.parseInt(config.getProperty("port")) muss man selbst parsen.
  • Nicht hierarchisch. Schlüssel wie db.primary.host sind nur Strings; die Punkte sind konventionell, nicht strukturell. Wenn eine echte Hierarchie benötigt wird, verwendet man eine YAML/JSON-Konfigurationsbibliothek.
  • Nicht thread-sicher für zusammengesetzte Operationen. Jede Methode ist synchronisiert (von Hashtable geerbt), aber Check-then-Act ist immer noch einer Race-Condition ausgesetzt. Dieselbe Einschränkung wie bei der Elternklasse.
  • Kein Ersatz für ResourceBundle für i18n. PropertyResourceBundle ist ein ResourceBundle, das durch eine .properties-Datei unterstützt wird und Locale-Lookup hinzufügt; das ist das richtige Werkzeug für übersetzte Strings.

Ein ausgearbeitetes Beispiel: Standardwerte laden, pro Umgebung überschreiben, zurückschreiben

Das folgende Programm erstellt eine geschichtete Konfiguration (Standardwerte im JAR, Umgebungsdatei außerhalb), liest eine System-Property als -D-Überschreibung, flacht das Ergebnis ab, sodass es zur Inspektion in einen .properties-Puffer geschrieben werden kann, und demonstriert die Nicht-String-store-Falle.

Eine Feinheit, die das Beispiel bewusst behandelt: store schreibt nur die eigenen Einträge eines Properties-Objekts — es durchläuft nie die geerbte defaults-Kette. Um eine vollständige, round-trippable Datei zu erhalten, kopieren wir daher jeden aufgelösten Schlüssel in ein flaches Properties-Objekt, bevor wir speichern, anstatt store auf einem Objekt aufzurufen, das sich auf sein defaults-Elternobjekt stützt.

java— editable, runs on the server

Was man aus dem Ergebnis mitnimmt:

  • Die dreilagige Konfiguration (Standardwerte → Umgebungsdatei → -D-Überschreibungen) löst sich korrekt auf. Standardwerte füllen auf, was niemand überschrieben hat; die Umgebungsdatei ändert log.level und feature.beta; das -D-Flag gewinnt für server.port.
  • store erzeugte einen portablen .properties-Text mit einem Kommentar und einem Zeitstempel am Anfang, der alle vier aufgelösten Schlüssel enthält. Man könnte diese Datei direkt wieder in load einlesen und dieselbe Map erhalten — weil wir die Ebenen zuvor abgeflacht haben.
  • setProperty("age", 30) würde nicht kompilieren (es erfordert einen String). put("age", 30) kompiliert, der Eintrag landet in der Tabelle, und stringPropertyNames filtert ihn heraus — aber store überspringt ihn nicht still: Es wirft eine ClassCastException, sobald es versucht, den Integer in String umzuwandeln. Die Lehre: Niemals einen Nicht-String in ein Properties-Objekt putten — immer setProperty verwenden.

Was kommt als Nächstes

Properties war das letzte "Datenstruktur"-Kapitel in diesem Teil. Die verbleibenden Kapitel handeln von Operationen auf Collections: ihre Durchquerung (Iterators und ListIterator), der Vergleich von Elementen (Comparable und Comparator) sowie die statischen Hilfsmethoden zum Sortieren, Suchen und Verpacken (die Collections-Klasse). Das nächste Kapitel beginnt mit dem Fundament — dem Iterator-Interface, das jede for-each-Schleife im Verborgenen nutzt.

Übungsaufgaben

Übung
Du schreibst `props.put('port', 8080)` (ein `Integer`) auf ein `Properties`-Objekt und rufst dann `props.store(out, null)` auf. Was passiert?
Du schreibst `props.put('port', 8080)` (ein `Integer`) auf ein `Properties`-Objekt und rufst dann `props.store(out, null)` auf. Was passiert?
Was this page helpful?