Imagine this: some upstream app calls your Spring Boot microservice asking for info about a "person."

Now this person could be a contractor or an employee. Those are the two types of persons recognized by the service.

So your Spring Boot application does a search for this person and finds a match. Hooray!

Then, the application uses Jackson to serialize the Java object and returns something like this:

{
  "id" : "A17",
  "lastName" : "Smith",
  "firstName" : "Frank"
}

Beautiful, right?

Wrong.

Is that "person" a contractor or an employee?

Although those two types of persons might share some similar characteristics, there are also plenty of differences.

Why leave it to the upstream app to figure out if the returned object represents a contractor or an employee?

Instead, you could do this:

{
  "employee" : {
    "id" : "A17",
    "lastName" : "Smith",
    "firstName" : "Frank"
  }
}

Now that's a little nicer, isn't it?

And yeah, you can do that easily with the magic of Jackson.

Start With an Annotation

Begin by using a Jackson annotation intuitively named @JsonRootName.

Let's take a look at an Employee class without that annotation.

public class Employee {

    private String id;    
    private String lastName;
    private String firstName;
    
    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String toString() {
        return ReflectionToStringBuilder.toString(this);
    }
}

That's a simple POJO that you've likely seen quite a few times. It defines just three (3) fields as well as related getters and setters.

Now serialize an instance of that class to a JSON object with this code:

ObjectMapper mapper = new ObjectMapper();            

Employee employee = new Employee();
employee.setId("A17");
employee.setFirstName("Frank");
employee.setLastName("Smith");

System.err.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(employee));

And you'll get this result:

{
  "id" : "A17",
  "lastName" : "Smith",
  "firstName" : "Frank"
}

But, once again, that does not tell you if the person is an employee or a contractor.

Fixing It

Now annotate that Employee class with @JsonRootName.

@JsonRootName(value = "employee")
public class Employee {

    private String id;    
    private String lastName;
    private String firstName;
    
    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String toString() {
        return ReflectionToStringBuilder.toString(this);
    }
}

Way up at the top of that class you'll see the following annotation: @JsonRootName(value = "employee")

That's telling Jackson to use "employee" as the root name when it serializes an Employee object. 

Now, let's run this code again:

ObjectMapper mapper = new ObjectMapper();            

Employee employee = new Employee();
employee.setId("A17");
employee.setFirstName("Frank");
employee.setLastName("Smith");

System.err.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(employee));

And then let's check the output:

{
  "id" : "A17",
  "lastName" : "Smith",
  "firstName" : "Frank"
}

But... that's not what we're looking for!

What happened? 

Well, you need to do a little more. Try this code instead:

ObjectMapper mapper = new ObjectMapper();            
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);

Employee employee = new Employee();
employee.setId("A17");
employee.setFirstName("Frank");
employee.setLastName("Smith");

System.err.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(employee));

See that second line? It's enabling a serialization feature that tells Jackson to wrap the whole Employee object in a root value.

And you defined that root value in the Employee class with @JsonRootValue

Now run that code again and you'll get this output:

{
  "employee" : {
    "id" : "A17",
    "lastName" : "Smith",
    "firstName" : "Frank"
  }
}

Excellent. Now that's what you're looking for.

So Why @JsonRootValue?

You might be asking yourself: "If I can use the SerializationFeature thing to include the root name, why do I need @JsonRootValue?"

To answer that question, conduct a simple experiment. Just comment out the @JsonRootValue line in the Employee class and rerun the serialization code.

You'll get this output:

{
  "Employee" : {
    "id" : "A17",
    "lastName" : "Smith",
    "firstName" : "Frank"
  }
}

See that? You've got a wrapped object but it's name begins with an uppercase "E".

You might (and probably don't) want some property names beginning with lowercase letters and others beginning with uppercase letters in your JSON object.

So that's why you would want to use @JsonRootName together with SerializationFeature.WRAP_ROOT_VALUE.

You could also use the annotation to completely change the name of the wrapped object. Maybe you'd want to use "PersonTypeEmployee" or something like that.

The possibilities are endless.

Wrapping It Up

Now you know how to send back a more descriptive JSON response to upstream apps.

Feel free to use what you've learned here today as you see fit.

Have fun!

Photo by Julia Volk from Pexels