Deserialize Java 8 LocalDateTime with JacksonMapper

151,778

Solution 1

The date time you're passing is not an ISO local date time format.

Change to

@Column(name = "start_date")
@DateTimeFormat(iso = DateTimeFormatter.ISO_LOCAL_DATE_TIME)
@JsonFormat(pattern = "YYYY-MM-dd HH:mm")
private LocalDateTime startDate;

and pass the date string in the format '2011-12-03T10:15:30'.

But if you still want to pass your custom format, you just have to specify the right formatter.

Change to

@Column(name = "start_date")
@DateTimeFormat(iso = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
@JsonFormat(pattern = "YYYY-MM-dd HH:mm")
private LocalDateTime startDate;

I think your problem is the @DateTimeFormat has no effect at all. As the Jackson is doing the deserialization and it doesn't know anything about spring annotation, and I don't see spring scanning this annotation in the deserialization context.

Alternatively, you can try setting the formatter while registering the Java time module.

LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);

Here is the test case with the deseralizer which works fine. Maybe try to get rid of that DateTimeFormat annotation altogether.

@RunWith(JUnit4.class)
public class JacksonLocalDateTimeTest {

    private ObjectMapper objectMapper;

    @Before
    public void init() {
        JavaTimeModule module = new JavaTimeModule();
        LocalDateTimeDeserializer localDateTimeDeserializer =  new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
        module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);
        objectMapper = Jackson2ObjectMapperBuilder.json()
                .modules(module)
                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .build();
    }

    @Test
    public void test() throws IOException {
        final String json = "{ \"date\": \"2016-11-08 12:00\" }";
        final JsonType instance = objectMapper.readValue(json, JsonType.class);

        assertEquals(LocalDateTime.parse("2016-11-08 12:00",DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") ), instance.getDate());
    }
}


class JsonType {
    private LocalDateTime date;

    public LocalDateTime getDate() {
        return date;
    }

    public void setDate(LocalDateTime date) {
        this.date = date;
    }
}

Solution 2

You used wrong letter case for year in line:

@JsonFormat(pattern = "YYYY-MM-dd HH:mm")

Should be:

@JsonFormat(pattern = "yyyy-MM-dd HH:mm")

With this change everything is working as expected.

Solution 3

This worked for me:

 @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ", shape = JsonFormat.Shape.STRING)
 private LocalDateTime startDate;

Solution 4

UPDATE:

Change to:

@Column(name = "start_date")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm", iso = ISO.DATE_TIME)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime startDate;

JSON request:

{
 "startDate":"2019-04-02 11:45"
}

Solution 5

There are two problems with your code:

1. Use of wrong type

LocalDateTime does not support timezone. Given below is an overview of java.time types and you can see that the type which matches with your date-time string, 2016-12-01T23:00:00+00:00 is OffsetDateTime because it has a zone offset of +00:00.

enter image description here

Change your declaration as follows:

private OffsetDateTime startDate;

2. Use of wrong format

There are two problems with the format:

  • You need to use y (year-of-era ) instead of Y (week-based-year). Check this discussion to learn more about it. In fact, I recommend you use u (year) instead of y (year-of-era ). Check this answer for more details on it.
  • You need to use XXX or ZZZZZ for the offset part i.e. your format should be uuuu-MM-dd'T'HH:m:ssXXX.

Check the documentation page of DateTimeFormatter for more details about these symbols/formats.

Demo:

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        String strDateTime = "2019-10-21T13:00:00+02:00";
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:m:ssXXX");
        OffsetDateTime odt = OffsetDateTime.parse(strDateTime, dtf);
        System.out.println(odt);
    }
}

Output:

2019-10-21T13:00+02:00

Learn more about the modern date-time API from Trail: Date Time.

Share:
151,778
Smajl
Author by

Smajl

Software developer with focus on Java and cloud technologies.

Updated on February 16, 2022

Comments

  • Smajl
    Smajl about 2 years

    I have read several questions with answers here in SO concerning serialization and deserialization between java.time.LocalDateTime and JSON property but I can't seem to get it working.

    I have managed to configure my Spring Boot Application to return the dates in the format I desire (YYY-MM-dd HH:mm) but I have problems accepting values in this format in JSON.

    These are all the things I have done so far:

    Added maven dependency for jsr310:

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

    Specified jsr310 in my main class:

    @EntityScan(basePackageClasses = { App.class, Jsr310JpaConverters.class })
    

    Disabled serialization as timestamps in application.properties:

    spring.jackson.serialization.write_dates_as_timestamps=false
    

    And this is my entity mapping for datetime:

    @Column(name = "start_date")
    @DateTimeFormat(iso = DateTimeFormat.ISO.TIME)
    @JsonFormat(pattern = "YYYY-MM-dd HH:mm")
    private LocalDateTime startDate;
    

    In my database, I store this date as TIMESTAMP in the following format: 2016-12-01T23:00:00+00:00.

    If I access this entity via my controller, it returns the JSON with correct startDate format. When I try to post it and deserialize it though, using YYYY-MM-dd HH:mm format, I get the following exception:

    {
      "timestamp": "2016-10-30T14:22:25.285+0000",
      "status": 400,
      "error": "Bad Request",
      "exception": "org.springframework.http.converter.HttpMessageNotReadableException",
      "message": "Could not read document: Can not deserialize value of type java.time.LocalDateTime from String \"2017-01-01 20:00\": Text '2017-01-01 20:00' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {MonthOfYear=1, WeekBasedYear[WeekFields[SUNDAY,1]]=2017, DayOfMonth=1},ISO resolved to 20:00 of type java.time.format.Parsed\n at [Source: java.io.PushbackInputStream@679a734d; line: 6, column: 16] (through reference chain: com.gigsterous.api.model.Event[\"startDate\"]); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type java.time.LocalDateTime from String \"2017-01-01 20:00\": Text '2017-01-01 20:00' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {MonthOfYear=1, WeekBasedYear[WeekFields[SUNDAY,1]]=2017, DayOfMonth=1},ISO resolved to 20:00 of type java.time.format.Parsed\n at [Source: java.io.PushbackInputStream@679a734d; line: 6, column: 16] (through reference chain: com.gigsterous.api.model.Event[\"startDate\"])",
      "path": "/api/events"
    }
    

    I know that there are many answers concerning this topic but following them and trying for couple of hours did not help me to figure out what am I doing wrong so I would be glad if someone could point out to me what am I missing. Thanks for any input on this!

    EDIT: These are all the classes involved in the process:

    Repository:

    @Repository
    public interface EventRepository extends PagingAndSortingRepository<Event, Long> {
    }
    

    Controller:

    @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Event> createEvent(@RequestBody Event event) {
            return new ResponseEntity<>(eventRepo.save(event), HttpStatus.CREATED);
    }
    

    My JSON request payalod:

    {
      "name": "Test",
      "startDate": "2017-01-01 20:00"
    }
    

    Event:

    @Entity
    @Table(name = "events")
    @Getter
    @Setter
    public class Event {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Column(name = "event_id")
        private long id;
    
        @Column(name = "name")
        private String name;
    
        @Column(name = "start_date")
        @DateTimeFormat(iso = DateTimeFormat.ISO.TIME)
        @JsonFormat(pattern = "YYYY-MM-dd HH:mm")
        private LocalDateTime startDate;
    }
    
  • Smajl
    Smajl over 7 years
    I have an error with this saying I cannot convert from DateTimeFormatter to DateTimeFormat.ISO
  • s7vr
    s7vr over 7 years
    Use the spring equalivent for that formatter. you get the idea right ?
  • Smajl
    Smajl over 7 years
    There is no such class called DateTimeFormatter in Spring. There is only DateTimeFormat (I mentioned it in my question) and i tried both formats with it (2011-12-03T10:15:30 and 2011-12-03 10:15). Neither worked
  • s7vr
    s7vr over 7 years
    okay. There has to be a way to pass the custom pattern to that datetimeformat annotation.
  • Smajl
    Smajl over 7 years
    I sure hope so, that's basically my question. I tried a lot of combinations but nothing worked so far (including @DateTimeFormat(pattern = "YYYY-MM-dd HH:mm"))
  • s7vr
    s7vr over 7 years
    I cant test it now. Can you try @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
  • Smajl
    Smajl over 7 years
    I did, see my comment above?
  • Smajl
    Smajl over 7 years
    No, that does not help
  • s7vr
    s7vr over 7 years
    Updated. Please take a look.
  • s7vr
    s7vr over 7 years
    Added a test case with LocalDateTime Deserailizer. Please take a look.
  • Smajl
    Smajl over 7 years
    Thank you for your effort. I made it work and will award bounty once it allows me to
  • Maciej Walkowiak
    Maciej Walkowiak over 7 years
    YYYY is just wrong. Do not overcomplicate your code and change it to yyyy
  • ankit
    ankit almost 6 years
    DateTimeFormatter.ISO_LOCAL_DATE throws compile time error : attribute value must be constant
  • pedram bashiri
    pedram bashiri almost 6 years
    I had the same problem as @Smajil , for some reason DateTimeFormat and JsonFormat were not effective (even though JsonFormat is a Jackson annotation). Many ppl suggested implementing my own custom Deserializer but I didn't want to implement one Deserializer for every Json class that I had. This answer saved my day, short sweet and effective
  • Spenhouet
    Spenhouet over 5 years
    As @ankit said this will not compile. Attribute value must be constant
  • ankit
    ankit over 5 years
    Please answer my question: stackoverflow.com/questions/52129200/…
  • chill appreciator
    chill appreciator over 4 years
    It just not compiles
  • Christian Meyer
    Christian Meyer over 4 years
    This is the wrong information. DateTimeFormatter is a different class than DateTimeFormat. DateTimeFormatter only has a select few enum choices, unlike DateTimeFormatter, which has a wide variety. And, by choosing DateTimeFormat.DATE_TIME, you wind up with millis despite the javadoc, which is why I am still looking for a solution.
  • Felipe Desiderati
    Felipe Desiderati over 4 years
    This part is worng: @DateTimeFormat(iso = DateTimeFormatter.ISO_LOCAL_DATE_TIME)
  • Anupam Jain
    Anupam Jain almost 4 years
    yup.. was looking for this thing. For that exact date-time value as mentioned in question "2016-10-30T14:22:25.285+0000", this is the solution which worked for me.
  • Xelian
    Xelian about 2 years
    Thanks this fix my parsing from 2022-02-21T17:57:53.049+0000. But what shape = JsonFormat.Shape.STRING mean?