Deserialize Java 8 LocalDateTime with JacksonMapper

JavaJsonJpaSpring BootJackson

Java Problem Overview


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;
}

Java Solutions


Solution 1 - Java

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 - Java

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 - Java

This worked for me:

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

Solution 4 - Java

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 - Java

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.

Solution 6 - Java

You can implement your JsonSerializer

See:

That your propertie in bean

@JsonProperty("start_date")
@JsonFormat("YYYY-MM-dd HH:mm")
@JsonSerialize(using = DateSerializer.class)
private Date startDate;

That way implement your custom class

public class DateSerializer extends JsonSerializer<Date> implements ContextualSerializer<Date> {
	
	private final String format;

	private DateSerializer(final String format) {
		this.format = format;
	}

	public DateSerializer() {
		this.format = null;
	}

	@Override
	public void serialize(final Date value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
		jgen.writeString(new SimpleDateFormat(format).format(value));
	}

	@Override
	public JsonSerializer<Date> createContextual(final SerializationConfig serializationConfig, final BeanProperty beanProperty) throws JsonMappingException {
		final AnnotatedElement annotated = beanProperty.getMember().getAnnotated();
		return new DateSerializer(annotated.getAnnotation(JsonFormat.class).value());
	}
	
}

Try this after post result for us.

Solution 7 - Java

This worked for me :

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;

@Column(name="end_date", nullable = false)
@DateTimeFormat(iso = ISO.DATE_TIME)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime endDate;

Solution 8 - Java

It works for me..

@Column(name = "lastUpdateTime")
@DateTimeFormat(iso = ISO.DATE_TIME,pattern = "yyyy-MM-dd HH:mm:ss" )	
@JsonIgnore

Solution 9 - Java

This one works for this JSON { "pickupDate":"2014-01-01T00:00:00" }

@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime pickupDate;

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSmajlView Question on Stackoverflow
Solution 1 - Javas7vrView Answer on Stackoverflow
Solution 2 - JavaMaciej WalkowiakView Answer on Stackoverflow
Solution 3 - Javauser5515View Answer on Stackoverflow
Solution 4 - JavaLeandroPLView Answer on Stackoverflow
Solution 5 - JavaArvind Kumar AvinashView Answer on Stackoverflow
Solution 6 - JavaMarcelo FerreiraView Answer on Stackoverflow
Solution 7 - JavaAlferd NobelView Answer on Stackoverflow
Solution 8 - JavaEnamul HaqueView Answer on Stackoverflow
Solution 9 - JavaInanc CakilView Answer on Stackoverflow