Reading and Writing YAML Files in Java with Jackson

Introduction

YAML files are nowadays being used extensively for defining properties of tools and applications due to the very human-readable syntax.

Besides containing configuration properties, they're also often used for data transmission/serialization, similar to how JSON is used.

Reading and writing YAML files is quickly becoming a basic developer skill, similar to how reading and writing JSON and XML files is.

What is YAML?

YAML Ain't Another Markup Language (YAML) is a simple, human-readable data-serialization language, very much alike JSON. It's easier to read and comprehend and has the ability to reference other items within a YAML file, as well as embed other formats such as JSON and XML within itself.

It's not meant to be a replacement for the current solutions and is mainly used for configuration files due to the fact that it's both easy to read and edit, and supports comments, making it a lot more team-friendly.

Most YAML files can be converted to JSON, and vice-versa as YAML is a superset of JSON. Here's an example of a YAML file:

--- #Employee Info
name: David
wage: 1500
position: Developer
techstack:
    - Java
    - Spring
    - Hibernate

The syntax is really simple, a dictionary (our employee entity) is represented with <key>: <value>.

After defining a few properties such as the name, wage, and position, a list is included, again, indented with each list item beginning with a -.

Each of these items can also be a dictionary as well:

---
name: David
wage: 1500
position: Developer
colleagues:
    -   name: Martha
        wage: 1500
        position: Developer
    -   name: Jim
        wage: 1800
        position: DevOps Engineer

You can shorten dictionaries and form more complex collections, though that's out of the scope of this tutorial.

With a refresher on YAML, we're ready to write some code that reads/writes YAML files. To achieve this, we can use either of the two popular libraries: Jackson or SnakeYAML. In this article, we'll be focusing on Jackson.

Reading YAML with Jackson

Jackson is an extremely popular Java-based library used for parsing and manipulating JSON and XML files.

Needless to say, it also allows us to parse and manipulate YAML files in a similar fashion to how we're already used to doing with the two previously mentioned formats.

By using Jackson as our library, we're in a familiar environment and many Java applications already have Jackson for other purposes anyway.

First, let's add Jackson to our project via Maven:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
    <version>{$version}</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>{$version}</version>
</dependency>

We'll need both jackson-databind from which we'll extract the ObjectMapper class and the jackson-dataformat-yaml dependency from which we'll extract the YAMLFactory class.

Just like we did with JSON and XML, the main class we'll be using for this task will be the ObjectMapper class. Its readValue() method is used to map the source (YAML file) into the result (an object of a class).

Let's first define a simple class that we'd like to map to. With respect to the previous YAML example, let's make an Employee class:

public class Employee {

    public Employee(String name, int wage, String position, List<Employee> colleagues) {
        this.name = name;
        this.wage = wage;
        this.position = position;
        this.colleagues = colleagues;
    }

    // Without a default constructor, Jackson will throw an exception
    public Employee() {}

    private String name;
    private int wage;
    private String position;
    private List<Employee> colleagues;

    // Getters and setters

    @Override
    public String toString() {
        return "\nName: " + name + "\nWage: " + wage + "\nPosition: " + position + "\nColleagues: " + colleagues + "\n";
    }

Now, we'll read a YAML file, say person.yaml, with the contents:

---
name: David
wage: 1500
position: Developer
colleagues:
    -   name: Martha
        wage: 1500
        position: Developer
    -   name: Jim
        wage: 1800
        position: DevOps Engineer

We'll map this file to an instance of our Employee class using Jackson's ObjectMapper class:

// Loading the YAML file from the /resources folder
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
File file = new File(classLoader.getResource("person.yaml").getFile());

// Instantiating a new ObjectMapper as a YAMLFactory
ObjectMapper om = new ObjectMapper(new YAMLFactory());

// Mapping the employee from the YAML file to the Employee class
Employee employee = om.readValue(file, Employee.class);

// Printing out the information
System.out.println("Employee info " + employee.toString());

// Access the first element of the list and print it as well
System.out.println("Accessing first element: " + employee.getColleagues().get(0).toString());

Running this piece of code will yield us:

Employee info
Name: David
Wage: 1500
Position: Developer
Colleagues: [
Name: Martha
Wage: 1500
Position: Developer
Colleagues: null
,
Name: Jim
Wage: 1800
Position: DevOps Engineer
Colleagues: null
]

Accessing first element:
Name: Martha
Wage: 1500
Position: Developer
Colleagues: null

Our person.yaml file was mapped to an instance of an object which we can then use for whatever is the intended purpose.

Writing YAML with Jackson

With reading and mapping out of the way, let's also go ahead and write to a YAML file.

As opposed to using the readValue() method, we'll be using the writeValue() method, specifying where the resulting file will end up and the object from which we're mapping into the YAML file.

Let's instantiate an employee and assign some values:

List<Employee> colleagues = new ArrayList<Employee>();

colleagues.add(new Employee("Mary", 1800, "Developer", null));
colleagues.add(new Employee("Jane", 1200, "Developer", null));
colleagues.add(new Employee("Tim", 1600, "Developer", null));
colleagues.add(new Employee("Vladimir", 1000, "Developer", null));

// We want to save this Employee in a YAML file
Employee employee = new Employee("David", 1500, "Developer", colleagues);

// ObjectMapper is instantiated just like before
ObjectMapper om = new ObjectMapper(new YAMLFactory());

// We write the `employee` into `person2.yaml`
om.writeValue(new File("/src/main/resources/person2.yaml"), employee);

Running this piece of code will yield us a YAML file with the contents:

---
name: "David"
wage: 1500
position: "Developer"
colleagues:
- name: "Mary"
  wage: 1800
  position: "Developer"
  colleagues: null
- name: "Jane"
  wage: 1200
  position: "Developer"
  colleagues: null
- name: "Tim"
  wage: 1600
  position: "Developer"
  colleagues: null
- name: "Vladimir"
  wage: 1000
  position: "Developer"
  colleagues: null

As you can see, the Java employee object was serialized in to a YAML-formatted file by using Jackson's support of YAML.

Conclusion

YAML-formatted files are becoming increasingly popular for defining properties of tools and applications due to the very human-readable syntax. Besides containing configuration properties, they're also being used more and more for data transmission, similar to how JSON is used, although their use in this area is still not as prevalent in the wild.

Jackson is an extremely popular Java-based library used for parsing and manipulating JSON and XML files. In addition, it was extended to allow developers to also work with the YAML file format.