17

After update from Spring Boot 2.4.5 to Spring 2.5.0 I noticed the following exceptions in the application logs:

Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.Instant` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: org.telegram.telegrambots.meta.api.objects.Update["my_chat_member"]->org.telegram.telegrambots.meta.api.objects.ChatMemberUpdated["new_chat_member"]->org.telegram.telegrambots.meta.api.objects.ChatMember["untilDateAsInstant"])
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1276) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer.serialize(UnsupportedTypeSerializer.java:35) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1514) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ObjectWriter._writeValueAndClose(ObjectWriter.java:1215) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:1059) ~[jackson-databind-2.12.3.jar!/:2.12.3]
    at org.springframework.jms.support.converter.MappingJackson2MessageConverter.mapToTextMessage(MappingJackson2MessageConverter.java:279) ~[spring-jms-5.3.7.jar!/:5.3.7]
    at org.springframework.jms.support.converter.MappingJackson2MessageConverter.toMessage(MappingJackson2MessageConverter.java:184) ~[spring-jms-5.3.7.jar!/:5.3.7]
    ... 37 common frames omitted

this is my pom.xml:

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

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>

I reverted to Spring Boot 2.4.5 and not everything works fine. What may be wrong with Spring Boot 2.5.0 ?

Updated

The corresponding GitHub https://github.com/spring-projects/spring-boot/issues/26859

alexanoid
  • 24,051
  • 54
  • 210
  • 410
  • 1
    Just adding the dependency is not enough. You need to configure the object mapper to add the Jackson module – Michael Jun 07 '21 at 15:34
  • 1
    But why it is working in SB 2.4.5 in stopped working in 2.5.0 ? – alexanoid Jun 07 '21 at 15:44
  • Could conceivably be [this](https://github.com/spring-projects/spring-boot/commit/b1f3d91f7df5dd311bf7e835cf6df003865a069c). If so, it's a bug – Michael Jun 07 '21 at 15:49
  • Kind of looks like the copy might screw things up. If you're appending your time module to the ObjectMapper after this copy happens, then the copied mapper won't pick up your change (obviously). Previously, you would just be mutating the instance held by the `JacksonClusterEnvironmentBuilderCustomizer`, which is fine so long as ObjectMapper is thread-safe, which it is – Michael Jun 07 '21 at 15:54
  • Thanks. So, should I wait for the newer version of SB in order to get it solved or how ? – alexanoid Jun 07 '21 at 15:57
  • 1
    Personally I would wait until the next release. In addition to this issue, 2.5.0 also broke our application due to: https://github.com/spring-projects/spring-boot/issues/26627 – Patrick Herrera Jun 10 '21 at 07:05
  • @PatrickHerrera Spring Boot 2.5.1 was released. Did this release solve the mentioned bugs? – alexanoid Jun 10 '21 at 21:07
  • Looks like the issue with 'Java 8 date/time type' has not yet been resolved – alexanoid Jun 11 '21 at 06:38
  • 2.4.5 and 2.5.1 use different versions of Jackson. I suspect that Maven may have corrupted the `jackson-datatype-jsr310` jar when it downloaded it. I would try clearing Maven’s local cache and building your application again. – Andy Wilkinson Jun 11 '21 at 07:02
  • For the record, the copying of the ObjectMapper is specific to Couchbase and, judging by the stack trace, it is JMS not Couchbase that’s involved here. – Andy Wilkinson Jun 11 '21 at 07:07
  • @AndyWilkinson thanks for your answer! I completely wiped my local Maven repo and redownloaded it from scratch. After this I'd like to confirm that the issue still exists – alexanoid Jun 11 '21 at 09:43
  • Thanks for trying that. Can you share a [minimal, reproducible example](/help/mcve)? – Andy Wilkinson Jun 11 '21 at 10:25
  • Unfortunately, I'm only able to catch such exception during execution of the application and receiving messages from Telegram API – alexanoid Jun 11 '21 at 14:30
  • 1
    For me - the /actuator/info endpoint stopped working after upgrading to sb 2.5.1, and fail with the same InvalidDefinitionException and jsr310 error regarding Instant type. Is the commit pointed by @Michael solved this issue ? – orid Jun 14 '21 at 11:59
  • The commit pointed to be @Michael is completely unrelated to the Actuator's info endpoint – Andy Wilkinson Jun 14 '21 at 12:15
  • I don't have `Jackson2ObjectMappearBuilder' on the application classpath in 2.4.5 and everything works correctly. Do I need to have `Jackson2ObjectMappearBuilder` on the classpath with SB 2.5.1 and if so, what Maven dependecy should I use for this purpose? – alexanoid Jun 14 '21 at 15:57
  • @Andy Wilkinson I'm testing right now, but looks like the issue is that I don't have `spring-web` Maven dependency on my application classpath. Anyway, I added `spring-web` Maven dependency to test this issue - but my Spring Boot application doesn't need `spring-web` because this is Telegram Bot – alexanoid Jun 14 '21 at 16:19
  • Unfortunately, adding spring-web to the classpath didn't help :( – alexanoid Jun 14 '21 at 16:43
  • If you weren’t using `spring-web` then Spring Boot won’t have been involved in configuring Jackson. Without it, `Jacskon2ObjectMapperBuilder`won’t be available and Boot’s Jackson auto-configuration will back off. – Andy Wilkinson Jun 14 '21 at 17:56
  • I added spring-web but it didn’t help with this issue – alexanoid Jun 14 '21 at 17:58
  • @alexanoid No, 2.5.1 didn't fix it for me, but it turns out the issue was that in some tests we were creating ObjectMappers from scratch that didn't have the right modules registered. Fixing that fixed the problem (and I didn't need to add `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` as an explicit dependency either). Still not sure why this was never an issue before, but this fix should prevent future issues – Patrick Herrera Jun 14 '21 at 22:18
  • @AndyWilkinson you were right. After deep investigation I found the following code in the 3rdparty library `= new ObjectMapper();` I added the following fix `ObjectMapper o = new ObjectMapper().registerModule(new JavaTimeModule());` and the issue is gone. Thank you! – alexanoid Jul 13 '21 at 11:50

5 Answers5

31

I saw an issue in one of my test classes. The problem there was it was creating a new ObjectMapper instance that was not adding the JavaTimeModule.

Here is a sample test that works in Spring 2.4.5 but fails in 2.5.0/2.5.1 with com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type java.time.ZonedDateTime not supported by default

It might be due to the upgrade in the jackson-datatype-jsr310 version

package net.jpmchase.gti.gtfabric;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

import java.time.ZonedDateTime;

public class ObjectMapperTest {

    @Test
    public void objectMapperTest() throws Exception{

        ZonedDateTime time = ZonedDateTime.now();
        ObjectMapper o = new ObjectMapper();
        o.writeValueAsString(time);
    }
}

To fix this particular test case had to add an explicit

ObjectMapper o = new ObjectMapper();
o.registerModule(new JavaTimeModule()); 
Saifuddin Merchant
  • 1,071
  • 7
  • 13
  • 1
    Try to use `Jackson2ObjectMapperBuilder` class to build instances of the `ObjectMapper` class: `Jackson2ObjectMapperBuilder.json().build()`. In this case you would obtain properly configured mappers. Wherever you have Spring Context, you could also inject a bean of `ObjectMapper` which Spring Boot exposes via `JacksonAutoConfiguration`. – xwandi Jun 18 '21 at 13:12
  • 2
    I had this problem evaluating values on unit test and this fixed it doing ```new ObjectMapper().registerModule(new JavaTimeModule()).writeValueAsString(value);``` :D thanks – TuGordoBello Nov 08 '21 at 15:49
  • @TuGordoBello aggree and advice to use the fluent-mutator to initialize in one line: [`new ObjectMapper().registerModule(new JavaTimeModule())`](https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/ObjectMapper.html#registerModule(com.fasterxml.jackson.databind.Module)). – hc_dev Nov 27 '21 at 20:17
  • 3
    This isn't an answer just tells you how to reproduce the issue. Doesn't help if your dependecies are using ObjectMapper. Nothing will stop spring from picking up the broken ObjectMapper. – Philip Rego Dec 23 '21 at 15:19
11

Perhaps you are not using in your code Object Mapper provided by spring.

Wrong way:

ObjectMapper o = new ObjectMapper();

Correct way:

@Autowired
Jackson2ObjectMapperBuilder mapperBuilder;

...

ObjectMapper mapper = mapperBuilder.build();
  • Can you explain, why the constructor use is considered wrong and with injected builder correct ? – hc_dev Nov 26 '21 at 11:09
  • 1
    If you are creating an instance yourself, you will need to configure everything yourself, for example add all the extensions you need. Using the builder provided by Spring, you have everything set up. – Dawid Świtoń-Maniakowski Nov 26 '21 at 17:23
  • Would add this great explanation to your answer ️ Spring's auto-configuration already registers available modules like `JavaTimeModule` to its default `ObjectMapper` bean. You can simply use this by adding a field to the test-class: `@Autowired ObjectMapper mapper;` (see [related answer](https://stackoverflow.com/a/64297951/5730279)). I would only use the _builder_ if Spring's auto-configured `ObjectMapper` is not sufficient and I need to [customize a separate one](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.spring-mvc.customize-jackson-objectmapper). – hc_dev Nov 27 '21 at 20:31
  • Not helpful because you can't change your depdencies code to use Jackson2ObjectMapperBuilder. – Philip Rego Dec 23 '21 at 15:20
2

In case you are using Spring Data Couchbase then this might be your problem: https://github.com/spring-projects/spring-data-couchbase/blame/4.2.x/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java#L309

The bug report is here: https://github.com/spring-projects/spring-data-couchbase/issues/1209

This has been fixed in Spring-Data-Couchbase 4.3

Tom
  • 1,965
  • 3
  • 25
  • 33
2

Posting a proper answer for the future me (that forgets):

@Configuration
public class JacksonConfig {

@Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, PlatformTransactionManager transactionManager) {
    return new DefaultBatchConfigurer(dataSource) {
        @Override
        protected JobRepository createJobRepository() throws Exception {
            Jackson2ExecutionContextStringSerializer serializer = new Jackson2ExecutionContextStringSerializer();
            ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()).findAndRegisterModules();
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
            serializer.setObjectMapper(objectMapper);

            JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
            factory.setDataSource(dataSource);
            factory.setTransactionManager(transactionManager);
            factory.setSerializer(serializer);
            return factory.getObject();
        }
    };
}

}

Source: https://docs.spring.io/spring-batch/docs/current/reference/html/job.html

LovaBill
  • 5,107
  • 1
  • 24
  • 32
0

While both Saifuddin Merchant and Dawid Świtoń-Maniakowski have provided different but equally working ways to make the library


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

actually work, my proposal is to remove it from your pom.xml completely and to write your own serializer (as an inner static class in this example):

public static class CustomDateSerializer extends com.fasterxml.jackson.databind.ser.std.StdSerializer<LocalDateTime> {
        private static final long serialVersionUID = 4365897561L;
        public CustomDateSerializer() { 
            this(null); 
        } 
        public CustomDateSerializer(Class<LocalDateTime> t) {
            super(t); 
        }
        @Override
        public void serialize(
                LocalDateTime value, JsonGenerator gen, SerializerProvider arg2) 
          throws IOException, JsonProcessingException {
            gen.writeString(value.format(DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm:ss")));
        }
    }

Next, annotate the time field of the object you want to serialize as JSON:

@JsonSerialize(using = CustomDateSerializer.class)
private LocalDateTime time = LocalDateTime.now();

Not much of code, right? The pom.xml dependency is 5 lines of code itself. But then autowiring Jackson2ObjectMapperBuilder is 2 lines, or if you decide to go with registering new JavaTimeModule() then it would be much more lines of code since this JavaTimeModule provides inappropriate date time serialization (as an array of numbers!) out of the box, so you would be in need to wire your own formatter into this JavaTimeModule somehow... not what you want probably.

Watch out for the generic type of the custom serializer - it is suited for <LocalDateTime> here. Just modify it to fit your needs.

Václav
  • 430
  • 1
  • 7
  • 22