Classe Properties en Java
Chargez et stockez des paires clé-valeur de chaînes en Java avec la classe Properties, y compris les fichiers .properties.
Properties est le conteneur du JDK pour la configuration chaîne-à-chaîne — paramètres d'application, substitutions d'environnement, messages localisés, paramètres de connexion JDBC. Elle étend Hashtable<Object, Object> (une décision historique que nous regrettons, mais avec laquelle nous sommes coincés) et ajoute trois choses par-dessus : un format de fichier .properties avec un lecteur et un écrivain, un lecteur et un écrivain XML, et le concept d'un objet Properties par défaut consulté lorsqu'une clé n'est pas trouvée localement.
System.getProperties() retourne un Properties. Chaque appel à System.getProperty("user.home") passe par lui. Une fois que vous avez vu la classe, vous la voyez partout.
Le contrat : des chaînes des deux côtés
Bien que la classe hérite d'une méthode put(Object, Object) de Hashtable, la seule API sûre est la paire typée en chaîne :
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 fallbackSi vous contournez setProperty et appelez put("server.port", 8080) avec un Integer, l'entrée atterrit quand même dans la table, mais vous avez posé une mine : stringPropertyNames() la filtre silencieusement, et les méthodes d'écriture de fichier (store, storeToXML) lancent une ClassCastException au moment où elles tentent de convertir cet Integer en String. Traitez Properties comme Properties<String, String> même si les génériques ne le disent pas.
Le format de fichier .properties
Texte brut, orienté ligne, key=value. Les espaces autour de = sont autorisés. Les lignes commençant par # ou ! sont des commentaires. Un antislash en fin de ligne continue la valeur sur la ligne suivante. Les échappements Unicode (\uXXXX) sont pris en charge, mais depuis Java 9 la surcharge load(Reader) lit nativement en UTF-8, donc vous en avez rarement besoin.
# server.properties — last edited 2026-05-12
server.host = localhost
server.port = 8080
server.path = /api/v1
greeting = Welcome, \
user!load et store gèrent ce format. loadFromXML et storeToXML gèrent le format XML équivalent défini par properties.dtd — il existe, occasionnellement utile, presque jamais préféré au format texte.
Chargement et stockage
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
}La méthode store écrit un commentaire d'horodatage après le commentaire utilisateur, ne trie rien (les entrées s'insèrent dans l'ordre d'itération de Hashtable), et échappe les caractères spéciaux (=, :, #, les espaces en début de ligne) pour un aller-retour fidèle. La sortie est portable entre les JVM.
Pour les ressources regroupées avec votre application, chargez depuis le classpath au lieu du système de fichiers :
try (var in = MyApp.class.getResourceAsStream("/app.properties")) {
config.load(in); // load(InputStream) defaults to ISO-8859-1
}load(InputStream) est la surcharge historique et utilise ISO-8859-1 (Latin-1) avec des échappements \u. load(Reader) utilise le jeu de caractères avec lequel le lecteur a été ouvert. Préférez la forme avec lecteur lorsque vous contrôlez l'encodage.
Propriétés par défaut : configuration en couches
Le getProperty(key, default) à deux arguments retourne une valeur de repli lorsque la clé est absente. Le constructeur Properties(Properties defaults) fait la même chose mais au niveau de l'objet — le second Properties est consulté lorsque le premier ne contient pas la clé :
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)C'est le modèle standard pour « des valeurs par défaut livrées avec l'application, l'utilisateur peut les remplacer par environnement ». Deux couches est le cas courant ; vous pouvez en chaîner davantage.
Propriétés System et flags -D
La JVM possède une instance globale de Properties accessible via System.getProperties() et System.getProperty(key). Les clés standard comprennent java.version, user.home, user.dir, os.name, file.separator et line.separator. Le flag -Dkey=value sur la ligne de commande JVM l'enrichit avant que main ne s'exécute :
java -Dserver.port=9090 -Dlog.level=DEBUG -jar app.jarString port = System.getProperty("server.port", "8080");C'est la « configuration en ligne de commande » la plus simple que vous puissiez donner à un programme Java. Pour des configurations plus importantes, la convention est un fichier .properties livré avec l'application, fusionné au démarrage avec les propriétés système (qui jouent le rôle de substitutions).
Ce que Properties n'est pas
- Pas une map de types arbitraires. Uniquement des chaînes. Parsez vous-même avec
Integer.parseInt(config.getProperty("port")). - Pas hiérarchique. Des clés comme
db.primary.hostne sont que des chaînes ; les points sont conventionnels, pas structurels. Si vous avez besoin d'une vraie hiérarchie, utilisez une bibliothèque de configuration YAML/JSON. - Pas thread-safe pour les opérations composées. Chaque méthode est synchronisée (héritée de
Hashtable), mais vérifier-puis-agir reste sujet aux races. Même mise en garde que la classe parente. - Pas un remplacement de
ResourceBundlepour l'i18n.PropertyResourceBundleest unResourceBundlesoutenu par un fichier.propertieset ajoute la recherche locale ; c'est le bon outil pour les chaînes traduites.
Un exemple concret : charger les valeurs par défaut, substituer par environnement, réécrire
Le programme ci-dessous construit une configuration en couches (valeurs par défaut à l'intérieur du JAR, fichier d'environnement à l'extérieur), lit une propriété système pour servir de substitution -D, aplatit le résultat afin qu'il puisse être réécrit dans un buffer .properties pour inspection, et démontre le piège des non-chaînes avec store.
Une subtilité que l'exemple gère délibérément : store n'écrit que les entrées propres d'un objet Properties — il ne parcourt jamais la chaîne defaults héritée. Donc pour obtenir un fichier complet et sérialisable en aller-retour, nous copions chaque clé résolue dans un Properties plat avant de stocker, plutôt que d'appeler store sur un objet qui repose sur son parent de valeurs par défaut.
Ce qu'il faut retenir de l'exécution :
- La configuration à trois couches (valeurs par défaut → fichier d'environnement → substitutions
-D) se résout correctement. Les valeurs par défaut remplissent ce que personne n'a substitué ; le fichier d'environnement modifielog.leveletfeature.beta; le flag-Dl'emporte pourserver.port. storea produit un texte.propertiesportable avec un commentaire et un horodatage en haut, contenant les quatre clés résolues. Vous pourriez réinjecter ce fichier directement dansloadet obtenir la même map — parce que nous avons d'abord aplati les couches.setProperty("age", 30)ne compilerait pas (il exige uneString).put("age", 30)compile bien, l'entrée atterrit dans la table, etstringPropertyNamesla filtre — maisstorene la saute pas silencieusement : il lance uneClassCastExceptiondès qu'il essaie de convertir l'IntegerenString. La leçon : ne jamais utiliserputavec une non-chaîne sur unProperties— utilisez toujourssetProperty.
La suite
Properties était le dernier chapitre sur les « structures de données » dans cette partie. Les chapitres restants portent sur les opérations sur les collections : les parcourir (Iterators et ListIterator), comparer les éléments (Comparable et Comparator), et les utilitaires statiques pour trier, rechercher et encapsuler (la classe Collections). Le prochain chapitre commence par la base — l'interface Iterator que chaque boucle for-each utilise secrètement.