| Module | Maven artifact | Adds support for |
JavaTimeModule | jackson-datatype-jsr310 | java.time.* — LocalDate, Instant, Duration, ZonedDateTime… |
ParameterNamesModule | jackson-module-parameter-names | Constructor/method parameter names without @JsonProperty |
Jdk8Module | jackson-datatype-jdk8 | Optional<T>, OptionalInt, OptionalLong… |
KotlinModule | jackson-module-kotlin | Kotlin data classes, default parameters, null safety |
GuavaModule | jackson-datatype-guava | Guava ImmutableList, Multimap, Optional… |
BlackbirdModule | jackson-module-blackbird | Bytecode-generated accessors — faster than reflection |
SimpleModule | Built-in | Your own serializers, deserializers, key (de)serializers |
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.2</version>
</dependency>
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.time.*;
ObjectMapper mapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
// Write dates as ISO strings, not epoch milliseconds
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
record Event(String name, LocalDate date, Instant createdAt, Duration duration) {}
Event event = new Event(
"Conference",
LocalDate.of(2025, 6, 15),
Instant.parse("2025-01-10T09:00:00Z"),
Duration.ofHours(8));
String json = mapper.writeValueAsString(event);
// {"name":"Conference","date":"2025-06-15","createdAt":"2025-01-10T09:00:00Z","duration":28800.000000000}
Event restored = mapper.readValue(json, Event.class);
System.out.println(restored.date()); // 2025-06-15
Without disable(WRITE_DATES_AS_TIMESTAMPS), Jackson serialises LocalDate as an array [2025,6,15] and Instant as a number. Always disable this feature for human-readable ISO-8601 output.
Java erases generic type parameters at runtime, so mapper.readValue(json, List.class) produces a raw List<LinkedHashMap>, not List<User>. Use TypeReference to preserve the generic information.
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.*;
ObjectMapper mapper = new ObjectMapper();
// WRONG — produces List<LinkedHashMap>, not List<User>
List rawList = mapper.readValue(json, List.class);
// CORRECT — TypeReference captures the full generic type at compile time
List<User> users = mapper.readValue(json, new TypeReference<List<User>>() {});
// Map
Map<String, User> userMap = mapper.readValue(json,
new TypeReference<Map<String, User>>() {});
// Nested generics
List<Map<String, List<Integer>>> complex = mapper.readValue(json,
new TypeReference<List<Map<String, List<Integer>>>>() {});
// With JavaType for dynamic construction
JavaType userListType = mapper.getTypeFactory()
.constructCollectionType(List.class, User.class);
List<User> users2 = mapper.readValue(json, userListType);
// Parameterized with two type params
JavaType mapType = mapper.getTypeFactory()
.constructMapType(HashMap.class, String.class, User.class);
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.17.2</version>
</dependency>
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import java.util.Optional;
ObjectMapper mapper = new ObjectMapper()
.registerModule(new Jdk8Module());
record Profile(String name, Optional<String> bio) {}
// With value
String json1 = mapper.writeValueAsString(new Profile("Alice", Optional.of("Developer")));
// {"name":"Alice","bio":"Developer"}
// Without value
String json2 = mapper.writeValueAsString(new Profile("Bob", Optional.empty()));
// {"name":"Bob","bio":null}
Profile p = mapper.readValue("{\"name\":\"Carol\",\"bio\":\"Writer\"}", Profile.class);
p.bio().ifPresent(System.out::println); // Writer
With ParameterNamesModule and the -parameters compiler flag, Jackson can match JSON fields to constructor parameters by name without requiring @JsonProperty on every parameter.
<!-- Maven: enable -parameters compiler flag -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs><arg>-parameters</arg></compilerArgs>
</configuration>
</plugin>
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import com.fasterxml.jackson.annotation.JsonCreator;
ObjectMapper mapper = new ObjectMapper()
.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
// No @JsonProperty needed — parameter names resolved at runtime
public record User(int id, String name, String email) {}
User user = mapper.readValue("{\"id\":1,\"name\":\"Alice\",\"email\":\"a@b.com\"}", User.class);
System.out.println(user.name()); // Alice
Spring Boot auto-configures ParameterNamesModule, JavaTimeModule, and Jdk8Module automatically when the jars are on the classpath. You only need to register them manually in plain Java applications.
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Currency;
// Custom serializer for java.util.Currency
class CurrencySerializer extends com.fasterxml.jackson.databind.ser.std.StdSerializer<Currency> {
CurrencySerializer() { super(Currency.class); }
@Override
public void serialize(Currency c, com.fasterxml.jackson.core.JsonGenerator gen,
com.fasterxml.jackson.databind.SerializerProvider p)
throws java.io.IOException {
gen.writeString(c.getCurrencyCode());
}
}
// Custom deserializer for java.util.Currency
class CurrencyDeserializer extends com.fasterxml.jackson.databind.deser.std.StdDeserializer<Currency> {
CurrencyDeserializer() { super(Currency.class); }
@Override
public Currency deserialize(com.fasterxml.jackson.core.JsonParser p,
com.fasterxml.jackson.databind.DeserializationContext ctx)
throws java.io.IOException {
return Currency.getInstance(p.getText());
}
}
// Assemble into a named module
SimpleModule currencyModule = new SimpleModule("CurrencyModule")
.addSerializer(Currency.class, new CurrencySerializer())
.addDeserializer(Currency.class, new CurrencyDeserializer());
ObjectMapper mapper = new ObjectMapper().registerModule(currencyModule);
record Payment(String id, Currency currency, double amount) {}
String json = mapper.writeValueAsString(new Payment("P1", Currency.getInstance("EUR"), 99.99));
// {"id":"P1","currency":"EUR","amount":99.99}
Payment p = mapper.readValue(json, Payment.class);
System.out.println(p.currency().getCurrencyCode()); // EUR