Java .properties Parser
A .properties file looks like the simplest configuration format there is, and it is the one hand-written parsers get wrong most often. This page runs the algorithm java.util.Properties.load actually uses — the JDK's LineReader, then load0 and loadConvert — over the text you paste or drop, and shows the resulting map key by key, as JSON, and as the file Properties.store would write back out. The parts people trip over are the ones it points at. The separator is =, : or a run of whitespace, so "c three" is the key c with the value three, and "key with spaces=x" is the key "key" with the value "with spaces=x". Whitespace around the separator disappears; whitespace at the end of a value does not, and nothing in an editor shows it. A backslash at the end of a line continues it and the next line's indentation is stripped — but an even number of trailing backslashes is not a continuation at all, and a comment line ending in a backslash does not continue either. # and ! open a comment only as the first non-blank character of a line, so the # in a URL fragment or after a value stays in the value. \t \n \r \f and \uXXXX are the only escapes; every other backslash is silently dropped, which is why \z is just z and why a Windows path has to be doubled. The biggest real-world bug in this format is the encoding. load(InputStream) — the call almost every framework makes — decodes the bytes as ISO-8859-1, so a file saved as UTF-8 turns each accented character into two or three others. Whenever the input contains non-ASCII, this page shows both readings and names the keys that change, so you can choose between \uXXXX escapes and load(Reader) with an explicit charset before it reaches production. Everything runs in your browser and nothing is uploaded. What the tool cannot tell you is which of two duplicate keys you meant, or whether a value that looks wrong is wrong: it reports what the JDK will do with the bytes in front of it, not what you intended to write.
Unique keys
11
Assignments
12
Comment lines
1
Continued lines
1
Findings
6
load(InputStream) is the default in almost every framework and always reads ISO-8859-1. Switch between the two readings to see which keys change.
Non-ASCII will mojibake · line 10
load(InputStream) decodes the bytes as ISO-8859-1, so a UTF-8 file turns each accented character into two or three Latin-1 ones. Escape them as \uXXXX, or load through a Reader with an explicit charset.
- greeting: "Grüße" → "GrüÃe"
Whitespace separator · line 4
There is no = or : on this line: the first run of spaces or tabs separates the key from the value, so "c three" is c = three.
app.mode
Trailing whitespace kept · line 9
Space around the separator is dropped, but space at the end of a value is part of the value. No editor shows it and no error mentions it.
app.token (+3)
Separator inside the key · line 6
This key contains an escaped =, : or space, so the line is one key and not two. Code that splits on the first "=" would cut it in the wrong place.
menu.label 1
Not a comment · line 14
# and ! start a comment only as the first non-blank character of a line. Here one sits after a value, so it stays inside the value.
note
Duplicate key · line 2, 12
The same key is assigned more than once. load keeps the last value and reports nothing; every earlier line is dead configuration that still looks live in a diff.
app.name → "orders-api-v2"
| Line | Key | Value | Separator | Notes |
|---|---|---|---|---|
| 1 | # Spring Boot style configuration — and every trap in the format | comment | ||
| 2 | app.name | orders-api | = | overwritten |
| 3 | server.port | 8080 | : | |
| 4 | app.mode | production | whitespace | Whitespace separator |
| 5 | jdbc.url | jdbc:postgresql://db:5432/orders?ssl=true | = | |
| 6 | menu.label 1 | Save as… | = | Separator inside the key |
| 7–8 | welcome.message | Hello and welcome | = | |
| 9 | app.token | abc123··· | = | Trailing whitespace kept |
| 10 | greeting | Grüße | = | |
| 11 | greeting.escaped | Grüße | = | |
| 12 | app.name | orders-api-v2 | = | |
| 13 | path | C:\\Users\\dev\\app | = | |
| 14 | note | value # this is not a comment | = | Not a comment |
The table escapes control characters (\n, \t) and marks trailing spaces with · so they are visible; the JSON view is exact.
How to use
- Paste the file into the box, or drop a .properties file onto the drop area — it is read in the browser and never uploaded.
- Read the per-line table: every logical entry with the line it came from, the separator that was actually used, and a badge for anything unusual on that line.
- Work through the findings above it. Duplicate keys, trailing whitespace and a continuation that swallowed the next entry are the three that quietly change behaviour in production.
- If the file has non-ASCII characters, switch the reading between UTF-8 and ISO-8859-1 to see exactly what load(InputStream) will hand your application.
- Copy the JSON view for a config diff, or the Properties.store view to get the same map written back with every separator, comment marker and non-ASCII character escaped for you.
Frequently asked questions
- Why is "c three" parsed as a key and a value? There is no = sign.
- Because whitespace is a separator in this format. Properties.load scans the line for the first unescaped =, : or whitespace character, whichever comes first, and treats everything before it as the key. So "c three" is the key c with the value three, and "key with spaces=x" is the key "key" with the value "with spaces=x" — the = never gets a chance to act as the separator. If you need a space in a key you must escape it as "\ ", which is exactly what Properties.store does when it writes such a key back out.
- My accented characters come out as é or �. What happened?
- java.util.Properties.load(InputStream) is specified to decode the stream as ISO-8859-1, one byte per character. A file saved as UTF-8 stores é as two bytes, and those two bytes become two Latin-1 characters — the classic mojibake. Nothing throws, so the corruption travels all the way to whatever renders the string. The three fixes are: write non-ASCII as \uXXXX escapes (what native2ascii and Properties.store produce), call load(Reader) with an InputStreamReader on UTF-8, or use the XML form loadFromXML, which is UTF-8 by default. This page shows both readings side by side so you can see which keys are affected before shipping.
- Does trailing whitespace in a value really survive?
- Yes, and it is the reason for a whole genre of bug reports. Leading whitespace on a line is dropped, whitespace around the separator is dropped, and whitespace at the start of a continuation line is dropped — but whitespace at the end of a value is kept verbatim, because the line ends there and nothing trims it. A password or URL with a stray space parses, connects to nothing, and looks identical to the working version in every editor and every code review. The table on this page marks those characters so you can see them.
- When does a line continue onto the next one?
- When it ends in an odd number of backslashes. The last backslash is removed, the next line is appended, and that next line's leading whitespace is stripped, so an indented continuation reads naturally. An even number of backslashes is not a continuation: they are literal backslashes, halved, and the entry ends there. Two things follow that surprise people. A continuation can swallow what looks like the next entry — if a value ends in a stray backslash, the "key=value" below it becomes part of that value and the key silently disappears. And a comment line ending in a backslash does not continue at all: comments are discarded whole, so the line after it is parsed as a normal entry.
- Two lines set the same key. Which one wins?
- The last one, silently. Properties is a Hashtable and load simply calls put for each entry as it goes, so a later line overwrites an earlier one with no warning, no error and no record that it happened. This is easy to create by accident when a file is assembled from several fragments, or when a key is defined once with = and once with : and the two do not look alike at a glance. This page lists every duplicated key with all the line numbers that set it and dims the ones that lost.
- Is this the same format as a .env file?
- No, and treating one as the other is a common source of quiet breakage. A .env file has no whitespace separator, uses quotes to protect values and to hold multi-line strings, treats a # after a value as an inline comment, and often supports ${VAR} expansion. A .properties file has none of that: quotes are ordinary characters that end up in the value, a # after a value is part of the value, there is no variable expansion in the format itself (Spring adds its own on top), and continuation is done with backslashes rather than quotes. The escapes differ too — .properties has only \t \n \r \f and \uXXXX.
Related tools
Text Encoding Converter
Open text files in legacy encodings (EUC-KR, Shift_JIS, Windows-1252…) as readable UTF-8.
Mbox Archive Viewer
Split a .mbox archive in your browser: message boundaries, decoded headers, MIME structure, threads and duplicate Message-IDs. Nothing is uploaded.
Java Class File Inspector
Read a compiled .class file in the browser: class file version and JDK release, access flags, constant pool, fields, methods and referenced classes.
BSON Decoder & Extended JSON Viewer
Decode a MongoDB BSON file in your browser: every element type with its type byte, exact int64 and decimal128, and canonical or relaxed Extended JSON.
JSON to Java Class (POJO) Converter
Turn a JSON object into Java POJO classes with type-mapped fields, in your browser.
String Escape Converter
Escape a string for 10 contexts at once — JavaScript, JSON, HTML entities, URL, SQL, regex, Bash (single/double quote), C string, Unicode \u escapes.