Skip to content

How to Create a Custom Jackson Date Serializer in Spring Boot

Purpose

This post shows how to create a custom Jackson date serializer in Spring Boot when you cannot use @JsonFormat annotations on auto-generated classes.

Environment

  • Spring Boot 2.x / 3.x
  • Jackson 2.x
  • Java 8+

The Problem

I needed to serialize java.util.Date fields in a custom format, but the DTO classes were auto-generated by an older system. Adding @JsonFormat annotations was not an option.

The property-based configuration spring.jackson.date-format has limitations:

  • Only supports a single global format
  • Doesn’t work when custom beans override auto-configuration
  • Cannot handle different formats for different fields

The Solution

Create a custom JsonSerializer<Date> and register it globally via Jackson2ObjectMapperBuilderCustomizer. This gives complete control over date serialization without modifying DTO classes.

Step 1: Create the Custom Serializer

CustomDateSerializer.java
package com.example.config;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CustomDateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
@Override
public void serialize(Date value, JsonGenerator gen,
SerializerProvider serializers) throws IOException {
String formattedDate = dateFormat.format(value);
gen.writeString(formattedDate);
}
}

Important: Import java.util.Date, not java.sql.Date. The serializer must match the field type in your POJOs.

Step 2: Register the Serializer Globally

Use Jackson2ObjectMapperBuilderCustomizer for a clean configuration:

JacksonConfig.java
package com.example.config;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Date;
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
builder.serializers(new CustomDateSerializer());
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
};
}
}

This approach:

  • Registers the serializer for all Date fields globally
  • Disables timestamp serialization as baseline
  • Works with Spring Boot’s auto-configuration

Step 3: Alternative with SimpleModule

For more control, use SimpleModule directly:

JacksonModuleConfig.java
package com.example.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilderCustomizer;
import java.util.Date;
@Configuration
public class JacksonModuleConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
SimpleModule module = new SimpleModule();
module.addSerializer(Date.class, new CustomDateSerializer());
builder.modules(module);
builder.featuresToDisable(
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
);
};
}
}

How It Works

  1. Serializer Registration: The custom serializer intercepts all Date serialization calls
  2. Format Control: The SimpleDateFormat inside the serializer defines the output format
  3. Global Application: Once registered, all Date fields use this format without code changes

Testing the Configuration

DateSerializationTest.java
package com.example;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
class DateSerializationTest {
@Autowired
private ObjectMapper objectMapper;
@Test
void shouldSerializeDateWithCustomFormat() throws Exception {
Date date = new Date(1704067200000L); // 2024-01-01 00:00:00 UTC
String json = objectMapper.writeValueAsString(date);
assertThat(json).contains("2024");
assertThat(json).doesNotContain(":"); // No timestamp format
}
}

Common Mistakes to Avoid

  1. Wrong import: Using java.sql.Date instead of java.util.Date
  2. Missing module registration: Not calling builder.modules(module)
  3. Timestamp baseline: Forgetting to disable WRITE_DATES_AS_TIMESTAMPS
  4. Thread safety: SimpleDateFormat is not thread-safe - create new instances or use ThreadLocal

Summary

In this post, I showed how to create a custom Jackson date serializer in Spring Boot when @JsonFormat annotations are not available. The solution uses JsonSerializer<Date> with Jackson2ObjectMapperBuilderCustomizer for global registration. This approach works well with auto-generated DTOs and provides full control over date formatting.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments