2023 answer using Spring Boot 3.1 (implies Hibernate 6.2)
Your Java class should be using Instant
. Like this:
@Column(name = "createdAt", columnDefinition="TIMESTAMP WITH TIME ZONE")
private Instant createdAt;
Hibernate will notice that you want to map to an Instant
and for such relation it will act as if the hibernate.jdbc.time_zone
property is set to UTC
(meaning you don't have to set this property). The methodology is safe to use meaning it will work consistently regardless of settings on the database server side or client side settings such as the default TZ of your own JVM. All these things are irrelevant when doing what I propose. And indeed you would want a methodology which is immune to such settings.
In the vast majority of cases what you really want to persist and read back is indeed an Instant
(not any of the other java.time
types). If you use say OffsetDateTime
or ZonedDateTime
in the persistence layer of your application then you give yourself an illusion that your persistence engine (in this case PostgreSQL) can do something which it really cannot.
Which database column type to use?
PostgreSQL doesn't store any time zone information with datatype TIMESTAMP WITH TIME ZONE
so it doesn't make any difference for your Java application if your database column uses the TIMESTAMP WITH TIME ZONE
type or the TIMESTAMP WITHOUT TIME ZONE
type. I would probably still use TIMESTAMP WITH TIME ZONE
as the values are a bit easier to interpret when I browse my table with some SQL browser tool. If you want to use an ANSI SQL type then just use TIMESTAMP
which is portable across database platforms (PostgreSQL will interpret it as TIMESTAMP WITHOUT TIME ZONE
). But again, for your Java application it is utterly irrelevant which of the two you use.
What about standards compliance?
The use of Instant
was somehow forgotten in the JPA Spec, but it seems it will finally find its way into the JPA 3.2 Spec. The reason why my example code currently works is because of Hibernate, not because of JPA. Still, I wouldn't think twice about using Instant
.
Can we never actually truly store a timestamp with timezone value?
A timezone specification can be either an offset (say +02:00
) or a timezone region id (say Europe/Paris
). First, you should realize that for everything that has already happened you might as well simply use an Instant. And the vast majority of use cases for timestamps in software are indeed for something which is in the past.
Offsets are in most cases pretty irrelevant to store as the database engine might as well simply first convert to the UTC offset (+00:00
) and then store that value. You would not lose any information by doing that. There is no problem in throwing away the offset.
Timezone region ids are another matter. They do make a lot of sense to persist, but only for things in the future, say for example for long financial contracts ("last payment to be made no later than 12:00 noon Europe/Paris time, 1st of Jan, 2053") or for scheduling information (run this job Mondays at 08:00 am, Europe/Paris time). However, PostgreSQL column types cannot persist such information. Neither can MS SQL Server or MySQL. I only know of Oracle Database which can be do this. If you really have such need in PostgreSQL then you would need to use two columns and then have your Java code collectively view them as one value of type ZonedDateTime
.
But your question is clearly about timestamps in the past ('created_at'), so your use case is simple: it is an Instant
!