The Jackson error “Java 8 date/time type not supported by default” means that the ObjectMapper has no serializer for java.time classes such as LocalDate, LocalDateTime or Instant, and we fix it by adding the jackson-datatype-jsr310 module and registering JavaTimeModule with the mapper. Jackson 3 has the module built in, so upgrading to Jackson 3 also removes the error.
The error shows up in Jackson 2.12 and later whenever code serializes or deserializes an object with a java.time field through a mapper created with new ObjectMapper(), for example in a unit test, a helper class, a Redis serializer or a Hibernate JSON column.
The following example registers the module on a Jackson 2 mapper and turns off timestamps, with the result of each line as a comment.
Employee employee = new Employee(1L, "Lokesh", LocalDate.of(1989, 5, 17));
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule()) // from jackson-datatype-jsr310
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // ISO text instead of [1989,5,17]
.build();
String json = mapper.writeValueAsString(employee); // {"id":1,"name":"Lokesh","dateOfBirth":"1989-05-17"}
String failed = new ObjectMapper().writeValueAsString(employee); // InvalidDefinitionException: Java 8 date/time type `java.time.LocalDate` not supported by default
Notice that the plain new ObjectMapper() still throws the exception, because the module fixes only the mapper it is registered with.
Next, we look at why Jackson 2 throws the error and fix it in two steps, or by upgrading to Jackson 3. After that, we fix the same error where a framework creates the mapper for us, such as Spring Boot and Hibernate JSON columns, and the FAQs cover the Spring Data Redis serializer.
1. Problem
The error occurs when we serialize a Java object or deserialize JSON to POJO, and the POJO contains new Java 8 date time classes such as LocalDate, LocalTime, LocalDateTime etc.
For example, the following Employee record has a LocalDate type field.
public record Employee(Long id, String name, LocalDate dateOfBirth) {}
When we serialize an instance of this record with Jackson 2.22.3, we get the following exception.
ObjectMapper mapper = new ObjectMapper();
Employee employee = new Employee(1L, "Lokesh", LocalDate.of(1989, 5, 17));
String json = mapper.writeValueAsString(employee); // throws InvalidDefinitionException
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDate` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (or disable `MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES`) (through reference chain: com.howtodoinjava.app.jackson.javatime.Employee["dateOfBirth"])
The message names the type that failed, and the reference chain at the end names the field. Reading JSON with readValue() throws the same InvalidDefinitionException. The method convertValue(), which converts an object to a Map, wraps the same message in an IllegalArgumentException.
| Type in the message | Example field | Failing call |
|---|---|---|
| java.time.LocalDate | date of birth | writeValueAsString(), readValue() |
| java.time.LocalDateTime | shift start | writeValueAsString(), readValue() |
| java.time.Instant | last login | writeValueAsString(), readValue() |
| any java.time type | any | convertValue() (as IllegalArgumentException) |
1.1. Why Jackson Says Java 8 Date/Time Type Not Supported by Default
The jackson-databind jar of Jackson 2 has no serializers for the java.time classes that came with Java 8. The serializers are in the separate jackson-datatype-jsr310 module, named after JSR 310, the specification of the java.time API.
Up to version 2.11, Jackson serialized a LocalDate like any other bean, through its getters. Since Jackson 2.12, the mapper throws the error instead, because the bean output was never a usable date. We can see that bean output when we disable MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES, which the message offers as the second option.
ObjectMapper beanStyle = JsonMapper.builder()
.disable(MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES)
.build();
String json = beanStyle.writeValueAsString(employee);
// {"id":1,"name":"Lokesh","dateOfBirth":{"year":1989,"month":"MAY","monthValue":5,"dayOfMonth":17,"leapYear":false,
// "dayOfWeek":"WEDNESDAY","dayOfYear":137,"era":"CE","chronology":{"id":"ISO","calendarType":"iso8601","isoBased":true}}}
No client expects a date in this shape, and the same mapper fails with an InvalidDefinitionException when it reads the JSON back into a LocalDate. So we always fix the error with the module.
2. Solution
We must add support to new Java 8 classes in two steps to fix this error.
First, add the latest version of com.fasterxml.jackson.datatype:jackson-datatype-jsr310 Maven dependency. Its version must match the jackson-databind version, which is 2.22.3 for the latest Jackson 2 release.
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.22.3</version>
</dependency>
Gradle builds add the same coordinates.
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.22.3'
Second, register the module JavaTimeModule either with ObjectMapper or JsonMapper based on what you are using.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
//or
JsonMapper jsonMapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.build();
//or, to register every Jackson module found on the classpath
ObjectMapper anyMapper = new ObjectMapper().findAndRegisterModules();
After registering the JavaTimeModule, the error goes away. The method findAndRegisterModules() finds the module through the Java ServiceLoader, so it works only when the jackson-datatype-jsr310 jar is on the classpath.
2.1. Writing Dates as ISO Text
With the module registered, Jackson 2 writes dates as numeric arrays by default, because SerializationFeature.WRITE_DATES_AS_TIMESTAMPS is enabled. An Instant becomes a decimal number of seconds since the epoch.
record Shift(String name, LocalDateTime start) {}
record Login(String user, Instant at) {}
Shift shift = new Shift("morning", LocalDateTime.of(2026, 10, 4, 9, 0));
Login login = new Login("lokesh", Instant.parse("2026-10-04T10:15:30Z"));
String date = objectMapper.writeValueAsString(employee); // {"id":1,"name":"Lokesh","dateOfBirth":[1989,5,17]}
String dateTime = objectMapper.writeValueAsString(shift); // {"name":"morning","start":[2026,10,4,9,0]}
String instant = objectMapper.writeValueAsString(login); // {"user":"lokesh","at":1791108930.000000000}
A JavaScript front end or another service expects ISO-8601 text such as “1989-05-17”, so we disable the feature.
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
String date = mapper.writeValueAsString(employee); // {"id":1,"name":"Lokesh","dateOfBirth":"1989-05-17"}
String dateTime = mapper.writeValueAsString(shift); // {"name":"morning","start":"2026-10-04T09:00:00"}
String instant = mapper.writeValueAsString(login); // {"user":"lokesh","at":"2026-10-04T10:15:30Z"}
String json = "{\"id\":1,\"name\":\"Lokesh\",\"dateOfBirth\":\"1989-05-17\"}";
Employee read = mapper.readValue(json, Employee.class); // Employee[id=1, name=Lokesh, dateOfBirth=1989-05-17]
When one field needs another format, we put @JsonFormat on it, e.g. @JsonFormat(pattern = “dd-MM-yyyy”) writes “17-05-1989”. The annotation needs the module too, so it is not a replacement for it. For more patterns and time zones, read serializing dates with Jackson.
3. Upgrading to Jackson 3
Jackson 3 never throws the error, because the java.time support is built into jackson-databind, so there is no module to add or register. Jackson 3 also disables WRITE_DATES_AS_TIMESTAMPS by default, so dates come out as ISO text.
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.2.3</version>
</dependency>
ObjectMapper mapper = new ObjectMapper();
String date = mapper.writeValueAsString(employee); // {"id":1,"name":"Lokesh","dateOfBirth":"1989-05-17"}
String instant = mapper.writeValueAsString(login); // {"user":"lokesh","at":"2026-10-04T10:15:30Z"}
The upgrade changes the package names from com.fasterxml.jackson to tools.jackson, except for the annotations, so it is a bigger step than adding one dependency. For a project that stays on Jackson 2, the two steps in section 2 are the fix.
4. With Spring Boot
Spring Boot creates an ObjectMapper bean and registers JavaTimeModule in it, because spring-boot-starter-json brings in jackson-datatype-jsr310. Spring Boot 3 also disables WRITE_DATES_AS_TIMESTAMPS, so REST controllers write “1989-05-17” without any setup. Spring Boot 4 uses Jackson 3 by default, which has java.time support built in.
So in a Spring Boot 3 application, the error points to code that creates its own mapper with new ObjectMapper(), such as a helper class or a test. We inject the Spring bean instead of creating a new mapper.
@Service
public class EmployeeExporter {
private final ObjectMapper mapper;
public EmployeeExporter(ObjectMapper mapper) { // the bean with JavaTimeModule
this.mapper = mapper;
}
public String export(Employee employee) throws JsonProcessingException {
return mapper.writeValueAsString(employee); // {"id":1,"name":"Lokesh","dateOfBirth":"1989-05-17"}
}
}
A plain JUnit test has no Spring context, so it builds the mapper itself with the module from section 2. For more on the Spring side of JSON, read about Spring Boot REST APIs.
5. With Hibernate 6 and 7
The above solution may not work if you are facing this issue due to Hibernate 6 that serializes the objects using FormatMapper instances, and the default is JacksonJsonFormatMapper. Hibernate uses the format mapper for entity fields mapped as JSON columns with @JdbcTypeCode(SqlTypes.JSON), and this mapper is not the Spring ObjectMapper bean.
For example, an Employee entity stores a Profile record with a LocalDate joinedOn field in a JSON column. When we persist the entity without the module, Hibernate 7.4.5 fails.
java.lang.IllegalArgumentException: Could not serialize object of java type: class demo.Main$Profile
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDate` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (or disable `MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES`) (through reference chain: demo.Main$Profile["joinedOn"])
The fix depends on the Hibernate version.
- Hibernate 6.0 to 6.2 create the default mapper with new ObjectMapper(), so adding the jar is not enough, and we need the custom format mapper in section 5.1.
- Hibernate 6.3 and later, including Hibernate 7, register every Jackson module found on the classpath. Adding the jackson-datatype-jsr310 dependency fixes the error, and the column stores {“team”:”Payments”,”joinedOn”:[2026,10,4]}.
- Hibernate 7 with only Jackson 3 on the classpath uses its Jackson3JsonFormatMapper, which supports java.time without the module.
5.1. A Custom FormatMapper
To control the JSON in the column, e.g. ISO dates instead of arrays, we need to create a custom instance of FormatMapper and assign it through a new property hibernate.type.json_format_mapper.
spring.jpa.properties.hibernate.type.json_format_mapper=com.howtodoinjava.JavaTimeJsonFormatMapper
And then create the mapper implementation as follows. The imports are for Hibernate 6.2 to 7.4. In Hibernate 6.1, the same classes are org.hibernate.type.FormatMapper and org.hibernate.type.jackson.JacksonJsonFormatMapper.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.format.FormatMapper;
import org.hibernate.type.format.jackson.JacksonJsonFormatMapper;
public class JavaTimeJsonFormatMapper implements FormatMapper {
private final FormatMapper delegate;
public JavaTimeJsonFormatMapper() {
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
this.delegate = new JacksonJsonFormatMapper(mapper);
}
@Override
public <T> T fromString(CharSequence text, JavaType<T> javaType, WrapperOptions options) {
return delegate.fromString(text, javaType, options);
}
@Override
public <T> String toString(T value, JavaType<T> javaType, WrapperOptions options) {
return delegate.toString(value, javaType, options);
}
}
With the custom mapper, Hibernate stores {“team”:”Payments”,”joinedOn”:”2026-10-04″} in the column and reads it back into the Profile record.
6. Java 8 Date/Time Error FAQs
6.1. How Do I Fix the Error in Spring Data Redis?
We register the module on the mapper inside the Redis serializer. The default GenericJackson2JsonRedisSerializer in Spring Data Redis 3.5 creates its own ObjectMapper without JavaTimeModule, so caching an object with a LocalDate field throws a SerializationException with the same message. The method configure() changes that internal mapper and keeps the type information that the serializer adds as @class.
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
serializer.configure(mapper -> mapper.registerModule(new JavaTimeModule()));
byte[] bytes = serializer.serialize(employee); // {"@class":"com.howtodoinjava.app.jackson.javatime.Employee","id":1,"name":"Lokesh","dateOfBirth":[1989,5,17]}
Object cached = serializer.deserialize(bytes); // Employee[id=1, name=Lokesh, dateOfBirth=1989-05-17]
6.2. Why Does the Error Still Appear After Adding the Dependency?
The dependency only puts the module on the classpath, and the mapper that throws the error was created without it. We register the module on that exact mapper or call findAndRegisterModules() on it. Also check that jackson-datatype-jsr310 has the same version as jackson-databind, because Jackson releases its modules together with jackson-databind and tests them only in matching versions.
6.3. Does @JsonFormat Fix the Error Without the Module?
No. The annotation @JsonFormat(pattern = “dd-MM-yyyy”) only sets the format for the serializer, and without the module there is no serializer for LocalDate. A plain new ObjectMapper() throws the same InvalidDefinitionException for an annotated field.
6.4. Should I Use JSR310Module or JavaTimeModule?
We use JavaTimeModule. The older JSR310Module class in the same jar is deprecated and replaced by JavaTimeModule.
7. Conclusion
The “Java 8 date/time type not supported by default” error comes from a Jackson 2 mapper without serializers for java.time. We add jackson-datatype-jsr310 with the same version as jackson-databind and register JavaTimeModule on every mapper that we create, and we disable WRITE_DATES_AS_TIMESTAMPS to get ISO text.
Spring Boot applications get a configured mapper bean, so we inject it instead of calling new ObjectMapper(). Hibernate JSON columns and Redis serializers have their own mappers, which need the module too. Jackson 3 has java.time support built in, so the upgrade removes the error from every Jackson 3 mapper.
8. References
- jackson-modules-java8 on GitHub
- Migrating to Jackson 3
- JavaTimeModule JavaDoc
- Hibernate ORM 7.4 FormatMapper JavaDoc
- Spring Boot JSON reference
Happy Learning !!