I'm encountering the following error when trying to return a custom ErrorResponse object with a LocalDateTime field using Jackson in my Spring Boot application:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: com.taskflow.taskflow.exceptions.ErrorResponse["timestamp"])
Problem:
Despite adding the necessary Jackson configuration, I still get the error indicating that LocalDateTime is not supported by default.
When I replace ErrorResponse with a Map<String, Object>, it works but that's not how i want to deal with error responses.
What I’ve tried:
Added jackson-datatype-jsr310 dependency.
Registered the JavaTimeModule in the ObjectMapper.
Configured @JsonFormat on the LocalDateTime field in ErrorResponse.
Question:
How can I properly serialize LocalDateTime with Jackson in my custom ErrorResponse class and fix the above error?
AuthenticationEntryPointJwt.java:
@Component
public class AuthenticationEntryPointJwt implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ErrorResponse errorResponse = new ErrorResponse(
HttpServletResponse.SC_FORBIDDEN,
"Forbidden",
"You do not have permission to access this resource.",
request.getRequestURI()
);
ErrorResponse.java:
@Getter
@Setter
public class ErrorResponse {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "UTC")
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
public ErrorResponse(int status, String error, String message, String path) {
this.timestamp = LocalDateTime.now();
this.status = status;
this.error = error;
this.message = message;
this.path = path;
}
}
I tried adding JacksonConfig but still same issue.
@Configuration
public class JacksonConfig {
@Bean
@Primary
public ObjectMapper objectMapper(){
return new ObjectMapper()
.registerModule(new JavaTimeModule()) // Enables LocalDateTime serialization
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // Ensures proper formatting
}
}
pom.xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.18.2</version>
</dependency>
You're getting the error:
Java 8 date/time type java.time.LocalDateTime
not supported by default
This happens because Jackson does not support LocalDateTime out of the box. You need to register the JavaTimeModule properly.
Solution
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.18.2</version>
</dependency>
Create a Jackson configuration class:
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return objectMapper;
}
}
@Getter
@Setter
public class ErrorResponse {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "UTC")
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
public ErrorResponse(int status, String error, String message, String path) {
this.timestamp = LocalDateTime.now();
this.status = status;
this.error = error;
this.message = message;
this.path = path;
}
}
@Component
public class AuthenticationEntryPointJwt implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper;
@Autowired
public AuthenticationEntryPointJwt(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ErrorResponse errorResponse = new ErrorResponse(
HttpServletResponse.SC_FORBIDDEN,
"Forbidden",
"You do not have permission to access this resource.",
request.getRequestURI()
);
// Convert ErrorResponse to JSON
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}
}