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
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:
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;
@Configurationpublic 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
Datefields globally - Disables timestamp serialization as baseline
- Works with Spring Boot’s auto-configuration
Step 3: Alternative with SimpleModule
For more control, use SimpleModule directly:
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;
@Configurationpublic 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
- Serializer Registration: The custom serializer intercepts all
Dateserialization calls - Format Control: The
SimpleDateFormatinside the serializer defines the output format - Global Application: Once registered, all
Datefields use this format without code changes
Testing the Configuration
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;
@SpringBootTestclass 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
- Wrong import: Using
java.sql.Dateinstead ofjava.util.Date - Missing module registration: Not calling
builder.modules(module) - Timestamp baseline: Forgetting to disable
WRITE_DATES_AS_TIMESTAMPS - Thread safety:
SimpleDateFormatis not thread-safe - create new instances or useThreadLocal
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