How to represent datetimes in APIs, the code, and the database is always tricky. Most people I have worked with struggled with this. The main mistake is omitting the time zone based on the false assumption that defaults are uniform across systems. They are not. As a result timestamps carried across systems get weird time jumps. This blog gives some rules to avoid these mistakes and gives a clean set up.

Counting the Time Zones

First of all people tend to forget how many systems and accompanying (default) time zones a typical configuration has. Suppose you have a:

  • browser on the user’s computer, running a single page application

  • server application with a REST API, running in a docker container

  • database, running in a docker container

Counting the (default) time zones settings:

  • browser computer: 1 (usually local time)

  • server container: 1 (default UTC, usually UTC or local time)

  • database container: 1 (default UTC, usually UTC)

  • database time zone setting: 1 (usually UTC or local time)

To complicate matters we also have daylight saving time.

Dropping the time zone from your timestamps and assuming the other system has the same default time zone is therefore very unreliable.

Preferred Configuration

My preferred configuration would be:

  • browser: local time

  • all other systems: UTC (by using UTC you avoid daylight saving time jumps)

But the following example should work for any time zone mix.

To keep the datetime format intact everywhere we should, assuming we have a Spring Boot application, use the following configuration.

Database

Use datetime field types with time zone. For example in PostreSQL you can use TIMESTAMPTZ (with time zone) instead of TIMESTAMP (no time zone). However, if you do use time zone-less timestamps the following approach still works. Furthermore set the following properties in your application.yml (NB Is crucial when you use time zone less timestamps!):

spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          time_zone: UTC

This settings will ensure that the correct conversion of datetime fields to/from the database even if you use a database field type that does not store the time zone.

Java Code

In the code you should use Instant for all datetime fields (has a fixed time zone: UTC). ZonedDateTime is also an option but less convenient. Do not use LocalDateTime, because that has no the time zone.

Example:

import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import org.hibernate.annotations.ColumnDefault;

import java.time.Instant;

@MappedSuperclass
public abstract class BaseEntity {

    @ColumnDefault("CURRENT_TIMESTAMP")
    @Column(name = "created_at", nullable = false, updatable = false)
    private Instant createdAt;

    @ColumnDefault("CURRENT_TIMESTAMP")
    @Column(name = "updated_at", nullable = false)
    private Instant updatedAt;

    @PrePersist
    public void setCreatedAt() {
        var now = Instant.now();
        createdAt = now;
        updatedAt = now;
    }

    @PreUpdate
    public void setUpdatedAt() {
        updatedAt = Instant.now();
    }
}

API

In the API prefer the ISO-8601 format. By using Instant in your code all you need is the following settings in application.yml:

spring:
  jackson:
    time-zone: UTC

Since Jackon 3 this is all you need to configure to write/read ISO-8601 formatted datetimes in your API. It will use nanosecond precision, but ISO-8601 removes trailing zero. So a typical timestam,p coudl look like: "2026-10-02T07:26:26.123456Z".

shadow-left