java.time
Serializing Java date and time objects to JSON is a common requirement in modern applications, especially in REST APIs and distributed systems. However, it comes with challenges—especially when using Java 8+ types from the java.time
package, such as LocalDate
, LocalDateTime
, and ZonedDateTime
. Without proper configuration, these types may not serialize as expected, or might not be deserialized correctly.
java.time
Out of the box, Java’s built-in serialization mechanisms (like java.io.Serializable
) are not sufficient for clean and portable JSON representations of date/time objects. For example, LocalDateTime
doesn't have a direct JSON equivalent. When left to default behavior, many JSON serializers either:
Therefore, the key to reliable serialization is to use a standard textual format—ISO-8601—which is already supported by the java.time
classes and widely understood across platforms and APIs.
Starting with Java 9+, the java.util.spi
package provides limited JSON support, but for most real-world scenarios, developers rely on third-party libraries like Jackson or Gson.
Let’s consider an example using Jackson, which is widely adopted and has built-in support for java.time
types via the jackson-datatype-jsr310
module.
LocalDateTime
Step 1: Include Jackson dependencies
<!-- For Maven -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.0</version>
</dependency>
Step 2: Register the Java Time module
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.LocalDateTime;
public class DateTimeJsonExample {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
LocalDateTime now = LocalDateTime.of(2025, 6, 22, 14, 30);
String json = mapper.writeValueAsString(now);
System.out.println("Serialized JSON: " + json);
LocalDateTime parsed = mapper.readValue(json, LocalDateTime.class);
System.out.println("Deserialized LocalDateTime: " + parsed);
}
}
Output:
Serialized JSON: "2025-06-22T14:30:00"
Deserialized LocalDateTime: 2025-06-22T14:30
This output uses the ISO-8601 standard format, which is the default for most java.time
types and is interoperable with most modern APIs and frontend applications.
JavaTimeModule
: Without this, Jackson may throw exceptions or serialize the date as an object with individual fields.LocalDateTime
don’t carry zone/offset info. For REST APIs, ZonedDateTime
or OffsetDateTime
are often better suited.JSON serialization of java.time
types requires deliberate configuration. Use libraries like Jackson with the JavaTimeModule
and prefer ISO-8601 formats for maximum compatibility. Always test both serialization and deserialization, especially when integrating with external systems, to ensure that time-related data is accurately and reliably preserved.
Serializing and deserializing Java 8+ date and time classes (LocalDate
, LocalDateTime
, ZonedDateTime
, etc.) using popular JSON libraries like Jackson and Gson requires additional configuration. These libraries do not natively handle java.time
types out of the box in older versions, and even in newer versions, explicit module registration or adapter setup is often needed to ensure consistent formatting, correct time zone handling, and compatibility with custom formats.
java.time
Jackson is one of the most widely used libraries for JSON processing in Java. To properly handle java.time
types, you must register the jackson-datatype-jsr310
module.
Maven Dependency:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.0</version>
</dependency>
ObjectMapper Configuration:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.time.LocalDateTime;
public class JacksonExample {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
LocalDateTime now = LocalDateTime.now();
String json = mapper.writeValueAsString(now);
System.out.println("Serialized LocalDateTime: " + json);
LocalDateTime parsed = mapper.readValue(json, LocalDateTime.class);
System.out.println("Deserialized LocalDateTime: " + parsed);
}
}
Output:
Serialized LocalDateTime: "2025-06-22T10:15:30"
Deserialized LocalDateTime: 2025-06-22T10:15:30
You can define a custom format using @JsonFormat
:
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
public class Event {
@JsonFormat(pattern = "dd-MM-yyyy HH:mm")
public LocalDateTime start;
}
This pattern ensures human-readable output and compatibility with systems expecting non-ISO formats.
java.time
Gson does not support java.time
classes by default. You must write or use custom TypeAdapter
s.
LocalDate
import com.google.gson.*;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class LocalDateAdapter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
@Override
public JsonElement serialize(LocalDate date, Type type, JsonSerializationContext context) {
return new JsonPrimitive(date.format(formatter));
}
@Override
public LocalDate deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
return LocalDate.parse(json.getAsString(), formatter);
}
}
Registering the Adapter:
Gson gson = new GsonBuilder()
.registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
.create();
LocalDate date = LocalDate.of(2025, 6, 22);
String json = gson.toJson(date);
System.out.println("Gson Serialized LocalDate: " + json);
LocalDate parsed = gson.fromJson(json, LocalDate.class);
System.out.println("Gson Deserialized LocalDate: " + parsed);
import com.google.gson.*;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class GsonLocalDateExample {
// Adapter to serialize/deserialize LocalDate as ISO-8601 string
public static class LocalDateAdapter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
@Override
public JsonElement serialize(LocalDate date, Type type, JsonSerializationContext context) {
return new JsonPrimitive(date.format(formatter));
}
@Override
public LocalDate deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
return LocalDate.parse(json.getAsString(), formatter);
}
}
public static void main(String[] args) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
.create();
LocalDate date = LocalDate.of(2025, 6, 22);
// Serialize LocalDate to JSON
String json = gson.toJson(date);
System.out.println("Gson Serialized LocalDate: " + json);
// Deserialize JSON back to LocalDate
LocalDate parsed = gson.fromJson(json, LocalDate.class);
System.out.println("Gson Deserialized LocalDate: " + parsed);
}
}
For classes like ZonedDateTime
or OffsetDateTime
, ensure your adapters or serializers handle zone and offset info correctly:
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/New_York"));
Using ISO-8601 format ensures offsets and zones are preserved:
"2025-06-22T10:15:30-04:00[America/New_York]"
With Jackson, this is handled automatically when using ZonedDateTime
and disabling timestamp output.
Library | Built-in Support | Custom Format | Time Zone Handling |
---|---|---|---|
Jackson | Yes (with JavaTimeModule ) |
Easy via @JsonFormat |
Excellent |
Gson | No (requires adapters) | Manual via TypeAdapter |
Custom handling needed |
When working with java.time
types in APIs or config files, prefer Jackson for ease and completeness, and ensure ISO-8601 compliance for portability. Always register the required modules or adapters and validate your output in multi-time-zone or multi-locale contexts.
Java provides built-in support for binary serialization through the Serializable
interface, and most classes in the java.time
package (such as LocalDate
, LocalDateTime
, ZonedDateTime
, etc.) implement this interface. This allows developers to persist date-time objects to disk or transmit them over networks in a binary format. However, while binary serialization is convenient, it comes with caveats related to compatibility, security, and maintainability.
To serialize an object containing java.time
fields:
import java.io.*;
import java.time.LocalDate;
class Person implements Serializable {
private static final long serialVersionUID = 1L;
String name;
LocalDate birthDate;
public Person(String name, LocalDate birthDate) {
this.name = name;
this.birthDate = birthDate;
}
}
Writing to a file:
Person p = new Person("Alice", LocalDate.of(1990, 6, 15));
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
out.writeObject(p);
}
Reading from a file:
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person loaded = (Person) in.readObject();
System.out.println(loaded.name + ": " + loaded.birthDate);
}
import java.io.*;
import java.time.LocalDate;
public class LocalDateSerializationExample {
// Person class with LocalDate field, implements Serializable
static class Person implements Serializable {
private static final long serialVersionUID = 1L;
String name;
LocalDate birthDate;
public Person(String name, LocalDate birthDate) {
this.name = name;
this.birthDate = birthDate;
}
}
public static void main(String[] args) {
String filename = "person.ser";
// Create a Person instance
Person p = new Person("Alice", LocalDate.of(1990, 6, 15));
// Serialize the object to a file
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filename))) {
out.writeObject(p);
System.out.println("Person object serialized to " + filename);
} catch (IOException e) {
System.err.println("Serialization error: " + e.getMessage());
e.printStackTrace();
}
// Deserialize the object from the file
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename))) {
Person loaded = (Person) in.readObject();
System.out.println("Deserialized Person:");
System.out.println("Name: " + loaded.name);
System.out.println("BirthDate: " + loaded.birthDate);
} catch (IOException | ClassNotFoundException e) {
System.err.println("Deserialization error: " + e.getMessage());
e.printStackTrace();
}
}
}
While Java’s serialization can persist the complete state of an object, it ties the binary format to the class structure, which introduces versioning challenges:
serialVersionUID
may cause InvalidClassException
.java.time
classes use internal implementation details (e.g., Ser
class), which may not remain stable across JVM versions.Best Practice: Always declare an explicit serialVersionUID
and avoid relying on automatic serialization for long-term storage.
Deserialization of untrusted data can lead to vulnerabilities such as remote code execution. This risk applies to any serialized object, including those containing java.time
fields.
Recommendations:
jdk.serialFilter
) to whitelist allowed classes.If you need more control over serialization (e.g., to avoid incompatibilities), implement Externalizable
or define custom writeObject()
/ readObject()
methods.
Example with custom logic:
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(birthDate.toString()); // Serialize as ISO-8601 string
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
String dateStr = (String) in.readObject();
birthDate = LocalDate.parse(dateStr); // Restore from string
}
This avoids potential issues if LocalDate
's internal structure changes in future JDKs.
serialVersionUID
explicitly.By applying these practices, you can ensure that your use of binary serialization with java.time
types remains robust, safe, and future-proof.