Classe Properties do Java
Carregue e armazene pares chave-valor string no Java com a classe Properties, incluindo arquivos .properties.
Properties é o contêiner do JDK para configuração string-para-string — configurações de aplicação, substituições de ambiente, mensagens localizadas, parâmetros de conexão JDBC. Ela estende Hashtable<Object, Object> (uma decisão histórica que hoje lamentamos, mas com a qual convivemos) e adiciona três coisas: um formato de arquivo .properties com leitor e escritor, um leitor e escritor XML, e o conceito de um objeto default de propriedades consultado quando uma chave não é encontrada localmente.
System.getProperties() retorna um Properties. Toda chamada a System.getProperty("user.home") passa por ele. Uma vez que você conhece a classe, a encontra em todo lugar.
O contrato: strings nos dois lados
Embora a classe herde um método put(Object, Object) de Hashtable, a única API segura é o par tipado como string:
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 fallbackSe você ignorar setProperty e chamar put("server.port", 8080) com um Integer, a entrada ainda será inserida na tabela, mas você terá plantado uma bomba-relógio: stringPropertyNames() a filtra silenciosamente, e os métodos de escrita em arquivo (store, storeToXML) lançam ClassCastException no momento em que tentam converter esse Integer para String. Trate Properties como Properties<String, String> mesmo que os genéricos não digam isso.
O formato de arquivo .properties
Texto simples, orientado a linhas, key=value. Espaços em branco ao redor de = são permitidos. Linhas começando com # ou ! são comentários. Barra invertida no final continua o valor na próxima linha. Escapes Unicode (\uXXXX) são suportados, mas desde o Java 9 a sobrecarga load(Reader) lê UTF-8 nativamente, então raramente são necessários.
# server.properties — last edited 2026-05-12
server.host = localhost
server.port = 8080
server.path = /api/v1
greeting = Welcome, \
user!load e store lidam com esse formato. loadFromXML e storeToXML lidam com o formato XML equivalente definido por properties.dtd — existe, às vezes é útil, mas quase nunca é preferido em relação à forma textual.
Carregando e armazenando
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
}O método store escreve um comentário de timestamp após o comentário do usuário, não ordena nada (as entradas ficam na ordem de iteração do Hashtable), e escapa caracteres especiais (=, :, #, espaços em branco no início) para permitir round-tripping. A saída é portável entre JVMs.
Para recursos empacotados com sua aplicação, carregue a partir do classpath em vez do sistema de arquivos:
try (var in = MyApp.class.getResourceAsStream("/app.properties")) {
config.load(in); // load(InputStream) defaults to ISO-8859-1
}load(InputStream) é a sobrecarga histórica e usa ISO-8859-1 (Latin-1) com escapes \u. load(Reader) usa o charset com o qual o reader foi aberto. Prefira a forma com reader quando você controla a codificação.
Propriedades padrão: configuração em camadas
O getProperty(key, default) com dois argumentos retorna um valor substituto quando a chave está ausente. O construtor Properties(Properties defaults) faz a mesma coisa, mas no nível do objeto — o segundo Properties é consultado quando o primeiro não contém a chave:
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)Esse é o padrão padrão para "defaults fornecidos com o app, o usuário pode sobrescrever por ambiente." Duas camadas é o caso comum; você pode encadear mais.
Propriedades do System e flags -D
A JVM tem uma instância global de Properties acessível via System.getProperties() e System.getProperty(key). Chaves padrão incluem java.version, user.home, user.dir, os.name, file.separator e line.separator. A flag -Dkey=value na linha de comando da JVM adiciona a ela antes que main execute:
java -Dserver.port=9090 -Dlog.level=DEBUG -jar app.jarString port = System.getProperty("server.port", "8080");Essa é a "configuração por linha de comando" mais simples que você pode dar a um programa Java. Para configurações maiores, a convenção é um arquivo .properties fornecido com o app, mesclado na inicialização com as propriedades do sistema (que atuam como substituições).
O que Properties não é
- Não é um mapa de tipos arbitrários. Apenas strings. Analise você mesmo com
Integer.parseInt(config.getProperty("port")). - Não é hierárquico. Chaves como
db.primary.hostsão apenas strings; os pontos são convencionais, não estruturais. Se você precisar de hierarquia real, use uma biblioteca de configuração YAML/JSON. - Não é thread-safe para operações compostas. Todo método é sincronizado (herdado de
Hashtable), mas verificar e agir ainda tem condição de corrida. Mesmo aviso da classe pai. - Não é substituto de
ResourceBundlepara i18n.PropertyResourceBundleé umResourceBundlebaseado em um arquivo.propertiese adiciona busca por locale; essa é a ferramenta correta para strings traduzidas.
Um exemplo completo: carregar defaults, sobrescrever por ambiente, salvar de volta
O programa abaixo constrói uma configuração em camadas (defaults dentro do JAR, arquivo de ambiente fora), lê uma propriedade do sistema para agir como uma substituição -D, achata o resultado para que possa ser salvo de volta em um buffer .properties para inspeção, e demonstra a armadilha do store com valor não string.
Uma sutileza que o exemplo trata deliberadamente: store escreve apenas as entradas próprias de um objeto Properties — nunca percorre a cadeia de defaults herdada. Portanto, para obter um arquivo completo e round-trippável, copiamos cada chave resolvida em um Properties plano antes de armazenar, em vez de chamar store em um objeto que depende de seu pai defaults.
O que extrair da execução:
- A configuração em três camadas (defaults → arquivo de ambiente → substituições
-D) resolve corretamente. Os defaults preenchem o que ninguém substituiu; o arquivo de ambiente mudalog.levelefeature.beta; a flag-Dvence paraserver.port. storeproduziu um texto.propertiesportável com um comentário e um timestamp no topo, contendo todas as quatro chaves resolvidas. Você poderia passar esse arquivo diretamente paraloade obter o mesmo mapa — porque achatamos as camadas primeiro.setProperty("age", 30)não compilaria (requer umaString).put("age", 30)compila, a entrada fica na tabela, estringPropertyNamesa filtra — masstorenão a ignora silenciosamente: ele lançaClassCastExceptionassim que tenta converter oIntegerparaString. A lição: nunca useputcom valor não string em umProperties— sempre usesetProperty.
O que vem a seguir
Properties foi o último capítulo de "estrutura de dados" desta parte. Os capítulos restantes são sobre operações em coleções: percorrê-las (Iterators e ListIterator), comparar elementos (Comparable e Comparator), e os utilitários estáticos para ordenar, pesquisar e encapsular (a classe Collections). O próximo capítulo começa com a fundação — a interface Iterator que todo loop for-each usa secretamente.