32

Im learning Spring Boot and I made a demo but when I POST a request to add a Object it didn't work!

The error message is:

{
    "timestamp": 1516897619316,
    "status": 400,
    "error": "Bad Request",
    "exception": "org.springframework.http.converter.HttpMessageNotReadableException",
    "message": "JSON parse error: Can not construct instance of io.starter.topic.Topic: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of io.starter.topic.Topic: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)\n at [Source: java.io.PushbackInputStream@1ff3f09a; line: 2, column: 9]",
    "path": "/topics/"
}

My Entity:

public class Topic {
    private String id;
    private String name;
    private String author;
    private String desc;

    public Topic(String id, String name, String author, String desc) {

        this.id = id;
        this.name = name;
        this.author = author;
        this.desc = desc;
    }
    //getters and setters

My controller:

public class TopicController {

    @Autowired
    private TopicService topicService;


    @RequestMapping(value = "/topics", method = RequestMethod.POST)
    public void addTopic(@RequestBody Topic topic) {
        topicService.addTopic(topic);
    }

My service:

@Service
public class TopicService {
    private List<Topic> topics = new ArrayList<>(Arrays.asList(
            new Topic("1", "topic1", "Martin", "T1"),
            new Topic("2", "topic2", "Jessie", "T2")  
            ));



    public void addTopic(Topic topic) {

        topics.add(topic);
    }

}

My json:

{
    "id": "3",
    "name": "topic3",
    "author": "Jessie3",
    "desc": "T3"
}

Please Help !

glytching
  • 44,936
  • 9
  • 114
  • 120
hackerang
  • 321
  • 1
  • 3
  • 3

2 Answers2

73

For deserialisation purposes Topic must have a zero-arg constructor.

For example:

public class Topic {
    private String id;
    private String name;
    private String author;
    private String desc;

    // for deserialisation
    public Topic() {}    

    public Topic(String id, String name, String author, String desc) {    
        this.id = id;
        this.name = name;
        this.author = author;
        this.desc = desc;
    }

    // getters and setters

}     

This is the default behaviour of the Jackson library.

glytching
  • 44,936
  • 9
  • 114
  • 120
  • provide the solution what you did. – a_a Aug 08 '18 at 08:40
  • Most of the case, the reason is, there is no default no-argument constructor as stated in this answer. – Levent Divilioglu Nov 29 '18 at 19:56
  • In case of FirstClass having secondClass as a property, this answer is the best, in conjunction with Andreas below. the FirstClass should have a JsonCreator annot on a constructor which takes the second object ( or List) as a parameter with JsonProperty annot. and the second Class needs to have an empty constructor with getters and setters for all the properties. – sarmahdi Apr 29 '19 at 07:05
  • 1
    Please don't use this solution, but use the one below. Any design where need to you need a default constructor for de-/serialization I'd consider extremely legacy. Don't compromise good design with well designed constructors, immutability etc. – Rohde Fischer Nov 16 '20 at 11:50
  • 1
    I'm using Lombok and I have @NoArgsConstructor added, but still same issue, Strangely happening in prod env and not in qa env, any pointers? – Deepanshu Rathi Sep 14 '21 at 09:05
  • Why JSON needs the default constractor? – java dev Nov 10 '21 at 07:52
14

You need to annotate the constructor with @JsonCreator:

Marker annotation that can be used to define constructors and factory methods as one to use for instantiating new instances of the associated class.

NOTE: when annotating creator methods (constructors, factory methods), method must either be:

  • 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

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.

Like this:

@JsonCreator
public Topic(@JsonProperty("id") String id, @JsonProperty("name") String name,
             @JsonProperty("author") String author, @JsonProperty("desc") String desc) {
    this.id = id;
    this.name = name;
    this.author = author;
    this.desc = desc;
}
Community
  • 1
  • 1
Andreas
  • 154,647
  • 11
  • 152
  • 247