Reading/writing Java properties files using Jackson (2.8)

I will still need to write more about Jackson 2.7 and 2.8 releases (last one I wrote about was 2.6), but before that let’s talk about one of biggest new things in 2.8: support for reading and writing plain old Java properties using `jackson-dataformat-properties`.

Jackson-supported data formats

Prior to 2.8 Jackson already had support for following data formats with official FasterXML data format modules (in order of addition):

  • JSON (0.5)

and there are a few others (see Jackson portal for details) that add support for other formats like BSON, MessagePack and Hocon.

So addition of yet another data format module does not seem like a big deal. Right?

Motivation

As things are, reading and writing Properties from/to files and resources (files stored in jars) is relatively simple. But actual use is cumbersome because access is through untyped (`Properties` is basically a `Map<String,String>`) and flat (no nested values) model: even if underlying information needs to contain typed things (say, URLs, numbers) and at least simple structures like Lists.

For example, you might have very simple configuration Properties file:

title=Home Page
site.host=localhost
site.port=8080

which could approximate contents of a JSON document like:

{ “title” : “Home Page”,
“site” : {
"host” : “localhost”,
“port” : 8080
}
}

and thereby map to a POJO like this:

public class Endpoint {
public String title;
public Site site;
}
public class Site {
public String host;
public int port;
}

but you’d still have to access values using awkward code like:

Properties props = ...;
int port;
String portStr = props.get("site.port");
if (portStr == null) {
... // report missing value
}
try {
port = Integer.parseInt(portStr);
} catch (NumberFormatException e) {
.... // handle
}

But this is not how things need to be since:

  1. Implied structure is commonly expressed using de-facto standard of dotted path, which is easily resolvable to intended structure

Because of these properties it is possible to relatively easily map actual contents into POJOs.
This is what `jackson-dataformat-properties` does.

Basic usage

Usage should be familiar to anyone who has used Jackson in general; the main difference is that a subtype of `ObjectMapper`(*) is to be used, but otherwise interface is the same:

JavaPropsMapper mapper = new JavaPropsMapper();
// and then read/write data as usual
Endpoint host = mapper.readValue(new File("site.properties"),
Endpoint.class);
String asText = mapper.writeValueAsString(host);

and there are some additional convenience methods too:

// Or, if you actually require `Properties` object you can use:
Properties props = mapper.writeValueAsProperties(host);
// or, convert a `Properties` to instance to POJO
Endpoint fromProps = mapper.readValue(props, Endpoint.class);

(*) Actually, you can construct plain `ObjectMapper` if you want, with:
ObjectMapper mapper = new ObjectMapper(new JavaPropsFactory());
— just make sure you give it different factory, and not default `JsonFactory`

Advanced Features

But wait! In addition to implied structure of contained POJO types, and simple conversions/coercions, support exists for arrays/Lists as well!
Consider following properties:

boxes.1.x = 5
boxes.1.y = 6
boxes.2.x = -5
boxes.2.y = 15

which could map to, say, a List (or array) of Point objects.
Or, maybe as an alternative:

boxes[1].x = 5
boxes[1].y = 6
boxes[2].x = -5
boxes[2].y = 15

And guess what? This is supported, by default. Just bind to something like:

public class Boxes {
public List<Box> boxes;
}
public class Box {
public int x, y;
}

Configurability

Examples above were relying on default settings, but the module allows quite a bit of configurability regarding how individual property values are constructed/deconstructed (modified to read/write).

Configurability is handled by another existing and widely used (with formats like CSV, Protobuf, Avro) interface of `FormatSchema`. For Properties, subtype to use is `JavaPropsSchema`, and you construct instances with a fluent-like methods:

JavaPropsSchema schema = JavaPropsSchema.emptySchema()
// Use brackets instead of dots for array index
.withWriteIndexUsingMarkers(true)
// and start from 3, not 1
.withFirstArrayOffset(3);
String props = propsMapper.writer(schema).writeValueAsString();

and get output like:

boxes[3].x = 5
boxes[3].y = 6
boxes[4].x = -5
boxes[4].y = 15

you may similarly configure other aspects like:

  • Line ending to use (default: “\n”)

Feedback Appreciated

Since this is a new module, not much feedback has yet been received. Usability and quality usually increase rapidly when users are using the component and reporting both problems, and suggestions for further improvements. So feedback would be appreciated via mailing lists, bug tracker, or Twitter.

Written by

Open Source developer, most known for Jackson data processor (nee “JSON library”), author of many, many other OSS libraries for Java, from ClassMate to Woodstox

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store