Spring Boot v4 includes an upgrade from Jackson v2 to v3, which includes many breaking changes. One of these change is the removal of ObjectMapper.registerModules. In v3 this could be used to register a Jackson module like so

@Configuration
public class JacksonConfiguration {
    public JacksonConfiguration(ObjectMapper objectMapper) {
        SimpleModule module = new SimpleModule();

        module.addSerializer(LocalDateTime.class, new JsonSerializer<>() {
            @Override
            public void serialize(LocalDateTime utcDateTime, JsonGenerator jsonGenerator,
                                  SerializerProvider serializers) throws IOException {
                String dateTimeWithZone = utcDateTime.atZone(ZoneOffset.UTC).toString();
                jsonGenerator.writeString(dateTimeWithZone);
            }
        });

        module.addDeserializer(LocalDateTime.class, new JsonDeserializer<>() {
            @Override
            public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
                String dateTimeWithZone = jsonParser.getValueAsString();
                return ZonedDateTime.parse(dateTimeWithZone).toLocalDateTime();
            }
        });

        objectMapper.registerModules(module);  // removed in Jackson v3
    }
}

In Jackson v3 registerModules has been removed because ObjectMapper is immutable.

Possibly one could replace the last line above with

objectMapper.rebuild().addModule(module)

An example of how to customise the ObjectMapper in v4 would be very useful, because I suspect this will be a breaking change for many users.

Update

I suspect the following may be a better solution. I've also updated the serializer and deserializer to the new APIs.

@Configuration
public class JacksonConfiguration implements JsonMapperBuilderCustomizer {

    @Override
    public void customize(JsonMapper.Builder jsonMapperBuilder) {
        SimpleModule module = new SimpleModule();

        module.addSerializer(LocalDateTime.class, new ValueSerializer<>() {
            @Override
            public void serialize(LocalDateTime utcDateTime, JsonGenerator jsonGenerator, SerializationContext ctx) {
                String dateTimeWithZone = utcDateTime.atZone(ZoneOffset.UTC).toString();
                jsonGenerator.writeString(dateTimeWithZone);
            }
        });

        module.addDeserializer(LocalDateTime.class, new ValueDeserializer<>() {
            @Override
            public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext context) {
                String dateTimeWithZone = jsonParser.getValueAsString();
                return ZonedDateTime.parse(dateTimeWithZone).toLocalDateTime();
            }
        });

        jsonMapperBuilder.addModule(module);
    }
}

Comment From: philwebb

Have you seen https://docs.spring.io/spring-boot/4.0/reference/features/json.html#features.json.jackson.custom-serializers-and-deserializers ?

Comment From: donalmurtagh

@philwebb I hadn't seen that before. It looks like a much simpler way of registering serializers/deserializers. Thanks very much for the link!