How to create default constructor for immutable class

You can use a Jackson factory (method annotated with @JsonCreator) that reads fields off a map and calls your non-default constructor:

class School {
    //fields

    public School(String id, String name) {
        this.schoolId = id;
        this.schoolName = name;
    }

    @JsonCreator
    public static School create(Map<String, Object> object) {
        return new School((String) object.get("schoolId"), 
                          (String) object.get("schoolName"));
    }

    //getters
}

Jackson will call the create method with a Map version of the json. And this effectively solves the problem.

I believe your question looks for a Jackson solution, rather than a new pattern/style.


TL;DR: using lombok and avoiding a default constructor

  • make immutable data class using @Value
  • annotate all your fields with @JsonProperty("name-of-property")
  • add lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty to your lombok.config to copy those to generated constructors
  • create an all-args constructor annotated with @JsonCreator

example:

@Value
@AllArgsConstructor(onConstructor_ = @JsonCreator)
class School {
    @JsonProperty("schoolId")
    String schoolId;
    @JsonProperty("schoolName")
    String schoolName;
}

long answer

There is an imo better alternative to a static factory method annotated with @JsonCreator, and that is having a constructor for all Elements (as is required for immutable classes anyway). Annotate that with @JsonCreator and also annotate all parameters with @JsonProperty like this:

class School {
    //fields

    @JsonCreator
    public School(
            @JsonProperty("id") String id,
            @JsonProperty("name") String name) {
        this.schoolId = id;
        this.schoolName = name;
    }

    //getters
}

Those are the options the @JsonCreator annotation gives you. It describes them like this in its documentation:

  • Single-argument constructor/factory method without JsonProperty annotation for the argument: if so, this is so-called "delegate creator", in which case Jackson first binds JSON into type of the argument, and then calls creator. This is often used in conjunction with JsonValue (used for serialization).
  • Constructor/factory method where every argument is annotated with either JsonProperty or JacksonInject, to indicate name of property to bind to

You might not even need to explicitly specify the parameter name under some circumstances. The documentation regarding that for @JsonCreator further states:

Also note that all JsonProperty annotations must specify actual name (NOT empty String for "default") unless you use one of extension modules that can detect parameter name; this because default JDK versions before 8 have not been able to store and/or retrieve parameter names from bytecode. But with JDK 8 (or using helper libraries such as Paranamer, or other JVM languages like Scala or Kotlin), specifying name is optional.

Alternatively this will also work nicely with lombok version 1.18.3 or up, where you can add lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty to your lombok.config and therefore have it copy the JsonProperty annotations to the constructor, given that you do annotate all fields with it (which one should do anyway imo). To put the @JsonCreator-annotation on the constructor, you can use the experimental onX feature. Using lombok's @Value for immutable data classes, your DTO then might just look like this (untested):

@Value
//@AllArgsConstructor(onConstructor = @__(@JsonCreator)) // JDK7 or below
@AllArgsConstructor(onConstructor_ = @JsonCreator) // starting from JDK8
class School {
    @JsonProperty("schoolId")
    String schoolId;
    @JsonProperty("schoolName")
    String schoolName;
}