CSV to Java Properties Converter

A CSV to .properties converter turns rows into the key and value pairs java.util.Properties reads. This one escapes the characters that would otherwise cut a key short, writes anything outside Latin-1 as a unicode escape so your accented text survives the loader, and builds dotted keys in the shape a Spring configuration expects. Nothing is uploaded.

Need to rename headings or drop columns first? Open the app

The file is Latin-1, whatever your editor says

This is the rule that surprises everyone, and it is still true. A .properties file loaded through Properties.load(InputStream) is decoded as ISO-8859-1. Not UTF-8, not the platform default. It is in the Javadoc, it has been there since the method existed, and it has not changed.

So a value of Café Münchner written as UTF-8 bytes comes back inside your application as Café Münchner. Nothing errors. The file loads, the string is wrong, and the bug is found by a customer looking at a label.

The safe form is a unicode escape: é for é, 東京 for 東京. Every loader reads those correctly regardless of how the bytes were decoded, which is why the Java tooling ships a native2ascii program whose entire job is producing them. That escaping happens here by default, and the count of affected values is reported.

If your application loads through Properties.load(Reader) with an explicit charset, or you are on a framework that reads properties as UTF-8, you can turn it off and get the raw characters. That is a real and increasingly common setup, which is why it is a switch rather than a rule.

Where a key ends

The second trap. A properties key ends at the first unescaped space, equals sign or colon. All three are separators, and a space counts.

So this line:

app name=billing

does not set app name to billing. It sets app to name=billing. Silently, with no warning from any loader, because it is a perfectly legal line.

Spaces in a column heading are turned into underscores here before the key is built, and anything left that would end a key, an equals sign, a colon, a hash or an exclamation mark, is escaped with a backslash so the whole name survives:

1.ratio\=x=1
1.host\:port=2

Values need less care but not none. A leading space is escaped, since it would otherwise be eaten as padding around the separator. Backslashes, tabs, carriage returns and form feeds all become escape sequences, and a newline becomes \n, because a backslash at the end of a line means continuation in this format and a raw newline would end the value.

A worked example

Two services:

name,replicas,cpu_limit,region_code
billing-api,4,1.5,01730
invoice-worker,2,0.5,00412

With name as the key column and app as the namespace:

# 2 rows as dotted keys
# Row keys come from the "name" column

app.billing-api.name=billing-api
app.billing-api.replicas=4
app.billing-api.cpu_limit=1.5
app.billing-api.region_code=01730
app.invoice-worker.name=invoice-worker
app.invoice-worker.replicas=2
app.invoice-worker.cpu_limit=0.5
app.invoice-worker.region_code=00412

That is the shape Spring's relaxed binding, Micronaut and most configuration libraries read into a map of objects. The region code keeps its zeros because everything is written as text, and a properties file has no numeric type to lose them to.

If two rows shared a name, the second would become app.billing-api_2.* with a note. A repeated key in a properties file means last-one-wins, so without the suffix an entire row would be quietly overwritten by the next.

Switching the separator to underscores gives app_billing-api_replicas, which is what you want when the same keys also have to work as environment variable names, since a dot is not legal in one.

Questions

Why are accented and CJK characters written as \u escapes?

Because a .properties file loaded through Properties.load with an InputStream is decoded as ISO-8859-1, not UTF-8. That is in the Javadoc and it has not changed. Writing UTF-8 bytes into such a file gives you mojibake in the running application rather than an error at load time. A \uXXXX escape is read correctly by every loader, so it is the safe form. If you load through a Reader with an explicit charset, you can turn the escaping off.

Where does a key end?

At the first unescaped space, equals sign or colon. That is the rule that catches people: a line reading app name=value sets the key app to the value name=value, silently. Spaces in a heading are turned into underscores here, and any equals sign, colon, hash or exclamation mark left in a key is escaped with a backslash so the whole name survives.

How are the keys built?

As an optional namespace, then a row key, then the column name, joined by dots or underscores. The row key comes from a column you pick, or from the row number when you do not. So with a namespace of app and an id column, you get keys like app.billing-api.replicas, which is the shape a Spring or Micronaut configuration expects.

What happens to duplicate row keys?

They get a numeric suffix and the count is reported. A repeated key in a .properties file means the last value wins, so without the suffix an entire row would be silently overwritten by the next one that shares its id. That is the same class of failure as duplicate INI sections and it is caught the same way.

What about line breaks and tabs in a value?

Written as escape sequences, which java.util.Properties reads back as the original characters, so nothing is lost. A raw newline would end the value, and a backslash at the end of a line means continuation in this format, so leaving them alone is not an option. A leading space in a value is escaped too, since it would otherwise be eaten as separator padding.

Should I use dots or underscores between key parts?

Dots are the Java convention and what Spring's relaxed binding, Micronaut and most configuration libraries expect. Underscores are useful when the same keys also have to work as environment variable names, since a dot is not legal there. Both are offered because which one is right depends entirely on what reads the file.

Is anything uploaded?

No. Everything runs in your browser tab, with nothing sent to a server, nothing kept between visits and no row cap. Configuration files usually carry internal hostnames and service names, so a converter that never receives them is the right default.

Convert your CSV to .properties

No sign-up, no upload, no row cap. Escaped separators, unicode escapes where Java needs them, keys in the shape Spring expects.

Back to the converter