JSON parse error: Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value

153,226

Solution 1

You need jackson dependency for this serialization and deserialization.

Add this dependency:

Gradle:

compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.4")

Maven:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

After that, You need to tell Jackson ObjectMapper to use JavaTimeModule. To do that, Autowire ObjectMapper in the main class and register JavaTimeModule to it.

import javax.annotation.PostConstruct;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

@SpringBootApplication
public class MockEmployeeApplication {

  @Autowired
  private ObjectMapper objectMapper;

  public static void main(String[] args) {
    SpringApplication.run(MockEmployeeApplication.class, args);

  }

  @PostConstruct
  public void setUp() {
    objectMapper.registerModule(new JavaTimeModule());
  }
}

After that, Your LocalDate and LocalDateTime should be serialized and deserialized correctly.

Solution 2

Spring Boot 2.2.2 / Gradle:

Gradle (build.gradle):

implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")

Entity (User.class):

LocalDate dateOfBirth;

Code:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
User user = mapper.readValue(json, User.class);

Solution 3

I had a similar issue which I solved by making two changes

  1. Added below entry in application.yaml file
spring:
    jackson:
        serialization.write_dates_as_timestamps: false
  1. Add below two annotations to the POJO's LocalDate field
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)

Example

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

public class Customer   {
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    protected LocalDate birthdate;
}

Eample request format:

{"birthdate": "2019-11-28"}

Example request format as array

{"birthdate":[2019,11,18]}

Solution 4

As it turns out, one should not forget to include jacson dependency into the pom file. This solved the issue for me:

<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

Solution 5

Well, what I do on every project is a mix of the options above.

First, add the jsr310 dependency:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

Important detail: put this dependency on the top of your depedencies list. I already see a project where the Localdate error persists even with this dependency on the pom.xml. But changing the order of the depedency the error was gone.

On your /src/main/resources/application.yml file, setup the write-dates-as-timestamps property:

spring:
  jackson:
    serialization:
      write-dates-as-timestamps: false

And create a ObjectMapper bean as this:

@Configuration
public class WebConfigurer {

    @Bean
    @Primary
    public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.build();
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        return objectMapper;
    }

}

Following this configuration, the conversion always work on Spring Boot 1.5.x without any error.

Bonus: Spring AMQP Queue configuration

Working with Spring AMQP, pay attention if you have a new instance of Jackson2JsonMessageConverter (common thing when creating a SimpleRabbitListenerContainerFactory). You need to pass the ObjectMapper bean to it, like:

Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(objectMapper);

Otherwise, you will receive the same error.

Share:
153,226

Related videos on Youtube

zavanton
Author by

zavanton

Updated on March 16, 2021

Comments

  • zavanton
    zavanton about 3 years

    I am new to Spring Data REST project and I am trying to create my first RESTful service. The task is simple, but I am stuck.

    I want to perform CRUD operations on a user data stored in an embedded database using RESTful API.

    But I cannot figure out how to make the Spring framework process the birthData as "1999-12-15" and store it as a LocalDate. The @JsonFormat annotation does not help.

    At present I get the error:

    HTTP/1.1 400 
    Content-Type: application/hal+json;charset=UTF-8
    Transfer-Encoding: chunked
    Date: Thu, 24 Aug 2017 13:36:51 GMT
    Connection: close
    
    {"cause":{"cause":null,"message":"Can not construct instance of java.time.LocalDate: 
    no String-argument constructor/factory method to deserialize from String value ('1999-10-10')\n 
    at [Source: org.apache.catalina.connector.CoyoteInputStream@4ee2a60e; 
    line: 1, column: 65] (through reference chain: ru.zavanton.entities.User[\"birthDate\"])"},
    "message":"JSON parse error: Can not construct instance of java.time.LocalDate: 
    no String-argument constructor/factory method to deserialize from String value ('1999-10-10'); nested exception is com.fasterxml.jackson.databind.JsonMappingException: 
    Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value ('1999-10-10')\n 
    at [Source: org.apache.catalina.connector.CoyoteInputStream@4ee2a60e; line: 1, column: 65] (through reference chain: ru.zavanton.entities.User[\"birthDate\"])"}
    

    How to make it work, so that client calls like:

    curl -i -X POST -H "Content-Type:application/json" -d "{  \"firstName\" : \"John\",  \"lastName\" : \"Johnson\", \"birthDate\" : \"1999-10-10\", \"email\" : \"[email protected]\" }" http://localhost:8080/users
    

    will actually store the entity into the database.

    Below is the information about the classes.

    The user class:

    package ru.zavanton.entities;
    
    
    import com.fasterxml.jackson.annotation.JsonFormat;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import java.time.LocalDate;
    
    @Entity
    public class User {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private long id;
    
        private String firstName;
        private String lastName;
    
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
        private LocalDate birthDate;
    
        private String email;
        private String password;
    
        public long getId() {
            return id;
        }
    
        public void setId(long id) {
            this.id = id;
        }
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public LocalDate getBirthDate() {
            return birthDate;
        }
    
        public void setBirthDate(LocalDate birthDate) {
            this.birthDate = birthDate;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    

    The UserRepository class:

    package ru.zavanton.repositories;
    
    import org.springframework.data.repository.PagingAndSortingRepository;
    import org.springframework.data.repository.query.Param;
    import org.springframework.data.rest.core.annotation.RepositoryRestResource;
    import ru.zavanton.entities.User;
    
    @RepositoryRestResource(collectionResourceRel = "users", path = "users")
    public interface UserRepository extends PagingAndSortingRepository<User, Long> {
    
        User findByEmail(@Param("email") String email);
    
    }
    

    Application class:

    package ru.zavanton;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
    
            SpringApplication.run(Application.class, args);
    
        }
    }
    
  • coretechie
    coretechie over 6 years
    What if even after adding these dependencies it is not working. Is there any way I can ask Json to ignore this param or something like that?
  • Tatha
    Tatha over 5 years
    I just had to add the gradle dependency to fix it
  • 0cnLaroche
    0cnLaroche over 4 years
    That part made it work for meobjectMapper.registerModule(new JavaTimeModule());
  • Felipe Desiderati
    Felipe Desiderati over 4 years
    There's no need to register the JavaTimeModule. It will be registered automatically if you have the dependency inside the Classpath.
  • Yoga Gowda
    Yoga Gowda about 4 years
    It looks like not registering automatically, I had to register explicitly to work.
  • Dhwanil Patel
    Dhwanil Patel about 4 years
    Thanks, I tried lot many solution but that are not working, but this one is best. How can i study and get more detail for the functionality of this class?
  • sparkyspider
    sparkyspider about 4 years
    The yyyy-mm-dd format is standardized under ISO 8601. JSR-310 specifies additions to the Java 8 Date and time API to incorporate ISO8601. The dependency shown above is a patch to add this functionality to Jackson versions prior to 2.85. Version 2.85 and onward support the Java Date and Time API. More info here: github.com/FasterXML/jackson-modules-java8/tree/master/datet‌​ime
  • DaddyMoe
    DaddyMoe over 3 years
    In addition to this correct answer note also, you can have the modules auto-discovered as suppose to manually register them. eg: new ObjectMapper().findAndRegisterModules(); That said, you should not really mix manual and auto registration if you do, only one of the registrations will have effect. Source: FasterXML jackson-modules-java8
  • DaddyMoe
    DaddyMoe over 3 years
    You can also have the modules auto-discovered as suppose to explicitly registering them. eg: new ObjectMapper().findAndRegisterModules(); That said, you should not really mix manual and auto registration if you do, only one of the registrations will have effect. Source: FasterXML jackson-modules-java8
  • farahm
    farahm over 3 years
    Im still getting the error. In my REST endpoint I am doing: Registration registration = mapper.convertValue(map.get("person"), Registration.class); What am I doing wrong?
  • Orkhan Hasanli
    Orkhan Hasanli over 3 years
    Thank you so much! You saved my time! I also added this line of code to pojo @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
  • CN1002
    CN1002 over 3 years
    This one looks clean to me.
  • Ventura
    Ventura over 3 years
    Thank you, as said above, simple and easy, works great
  • xlm
    xlm about 3 years
    FWIW I found that modifying the application.yaml was not needed but it was necessary to add dependency jackson-datatype-jsr310 and also include @JsonFormat as suggested
  • Katia Savina
    Katia Savina over 2 years
    I've just registered the JavaTimeModule without adding the dependency, and it works! Had some Jackson dependencies though..