Why JPA does not support java.time.Instant?
Asked Answered
A

5

36

I think that java.time.Instant is the best choice to store a date into DB: it is the most likely TIMESTAMP and you are not depending by timezone, it is just a moment on the time.

JPA supports LocalDate, LocalTime, LocalDateTime etc. but not Instant. Sure, you can use either AttributeConverter or some libraries like Jadira but why it isn't supported out of the box?

Aurignacian answered 15/3, 2018 at 20:58 Comment(5)
Note that it is supported in Hibernate 5.Selfdenial
I know. But today in discussion with my colleagues appeared an idea that you should not use Instant because JPA does not support it. And I want to know why?Aurignacian
DataNucleus JPA has supported it since before Java 8! The simple fact is that Oracle got lazy with the JPA API and couldn't be bothered committing resource to it, hence JPA 2.2 had very few items in it, and that is a type they left outCleon
RE: your first sentence: (a) Yes, use java.time.Instant to represent a moment, such as a SQL-standard TIMESTAMP WITH TIME ZONE column. And yes, Instant replaces both java.util.Date & java.sql.Timestamp classes. (b) No, “not depending by timezone” is incorrect. There is a time zone involved on an Instant: UTC. As for the database, that behavior varies. For example, in Postgres, the input to a TIMESTAMP WITH TIME ZONE column uses any provided zone/offset info to adjust into UTC for storage, then discarding that provided zone/offset info after the adjustment is complete.Liew
Ignore the wrong advice seen on this page and elsewhere about using java.time.LocalDateTime. That class does not represent a moment as it purposely lacks any concept of zone/offset. That class is indefinite, and represents a rough idea about potential moments along a range of about 26-27 hours (the range of time zones around the globe).Liew
C
15

I'll try this again. There is some discussion in the issue. The latest discussion seems to be:

mkarg said: While that is absolutely correct, the technical answer is a bit more complex: What is the final predicate that makes a data type eligible for inclusion in the set of mandatory type mappings?

One could say, that predicate is "being essential" or "being of common use", but who defines what "essential" or "common use" is? See, for some applications, support for java.awt.Image and java.net.URL might be much more essential than support for LocalDate or ZonedDateTime. On the other hand, other applications might be full of LocalDate but never uses Instant. So where exactly to make the cut? This becomes particularly complex when looking at the sheer amount of types found in the JRE, and it is obvious there has to be a cut somewhere. Even JavaFX, which is bundled with the JRE, does not support Instant still in v8, so why should JPA? And looking at the current progress of Project Jigsaw, possibly the qualifying predicate might simply be answered by "all types in a particular jigsaw module"?

Anyways, it is not up to me to decide. I do support your request, and would love to see support for rather all Java Time API times, particularly for Instant and Duration, and your request has prominent supporters like for example Java Champion Arun Gupa as I learned recently. But I doubt the final answer will be as simple an satisfying as we would love to have it.

Maybe it would be better to simply set up another JSR, like "Common Data Type Conversions for the Java Platform", which provides much more mappings than just date and time, but also would not be bound to JPA but also could be used by JAXB, JAX-RS, and possibly more API that deal which the problem of transforming " to "? Having such a vehicle would really reduce boilerplate a lot.

TL-DR; There are a lot of types. We had to draw the line somewhere.

There is a new issue for it to be added to a future JPA version.

Another interesting bit of analysis I found on a thread by Douglas Surber (works on JDBC):

The JDK 8 version of JDBC includes support for most of the SQL types that correspond to 310 classes.

  • DATE - LocalDate
  • TIME - LocalTime
  • TIMESTAMP WITH OUT TIME ZONE - LocalDateTime
  • TIMESTAMP WITH TIME ZONE - OffsetDateTime

JDK 8 version of JDBC does not include a mapping between the INTERVAL types and the corresponding 310 classes.

There is no SQL type that exactly corresponds to any other 310 classes. As a result, the JDBC spec is silent for all other classes.

I would strongly encourage JDBC developers to use the new 310 classes. There are problems with java.util.Date, java.sql.Date, java.sql.Time, and java.sql.Timestamp. You should consider them deprecated. The 310 classes are vastly superior.

Douglas

TL:DR; We just picked one Java 8 type for each of the 4 possible ways you might store temporal data in the database.

Finally, if you read through this thread it appears there is significant cultural pressure to keep standard APIs small and simple.

Crackling answered 15/3, 2018 at 21:11 Comment(7)
For me storing LocalDate (LocalTime, LocalDateTime) isn't a good way cause they are using TimeZone but aren't store it, so your application should be used in one TimeZone, or you should store the timezone in other column or (I think most used case) store this date in UTC and on each request transform it to needed TimeZone.Aurignacian
If you need the time zone stored then use OffsetDateTime.Crackling
The problem is that I don't need specific TimeZone, the TimeZone I'll get from browser and I'll adapt the Instant to the needed TimeZoneAurignacian
Ok. I read your comment backward. If you want to store all your times in UTC then just do LocalDateTime.ofInstant(instant, ZoneOffset.UTC);Crackling
In a twisted way this is actually more flexible than Instant because, if your database already had a lot of existing data (or another application was creating data) which was stored as a timestamp without time zone but not in UTC then JPA wouldn't know how to map it to an instant. In this way, if you are mapping a column that doesn't have time zone information, you have to specify the time zone that data was stored in.Crackling
That is not a "new issue" you refer to. It is an old issue which requested ALL sensible java.time types be supported, and as it shows, the Oracle representative unilaterally decided that he would only support a subset in the JPA 2.2 spec. The "new issue" is this one github.com/javaee/jpa-spec/issues/163Cleon
They drew a line in the wrong place. The default java.time class is now Instant, all others are additional. So if they had to drew a line it would be to use just Instant and later add others.Adenaadenauer
C
5

That is a $ 1M question.

Why it wasn't supported in the past, I don't know. It is indeed odd. However, it seems it might become part of upcoming JPA 3.2 Spec. In my opinion Instant should have been the first JSR-310 type to support - not the last coming along 10 years later.

Here is some background:

Almost all RDBMSes can indeed only store an Instant. Some, like PostgreSQL timestamptz, will give you the illusion that they can do more, but it is really a hoax as pointed out by many others.

If your entity class looks like this

@Entity
public class MyEntity {
  ...
  @Column(name="created_at")
  private ZonedDateTime createdAt;

  public ZonedDateTime getCreatedAt() {
      return this.createdAt;
  }
  public void setCreatedAt(ZonedDateTime ts) {
      this.createdAt = ts;
  }
}

you are most likely doing it wrong: With this, your persistence layer code is giving you the illusion that it can store a ZonedDateTime for you while in reality it can't. (the notable exception is Oracle Database which can actually store a ZonedDateTime without loss of information, but I must admit I've never seen it used in real life).

I've found this comment:

Instant is not covered by JDBC, so is not by JPA. At least not yet.

which may explain why the JPA maintainers did not (until recently) acknowledge why it should be mentioned in the JPA Spec. But the above statement is wrong. Support for Instant in JDBC has been there all along. Let me explain:

Reading and writing Instant values using JDBC

The JDBC java.sql.Timestamp and Instant is essentially the same thing. Except for some edge cases caused by the fact that Instant can store dates further in the past or into the future than can Date (which Timestamp extends from) there is a lossless conversion between the two. (edge cases: we are talking about dates in future year 292,469,238 and beyond which would get you into trouble and similar nonsense on the past side, so yes, for all practical purpose there is lossless conversion between the two).

So all that is left for us is to explain to the JDBC driver that we provide and expect values in UTC.

Suppose we have

private static final Calendar UTC_CAL = Calendar.getInstance(TimeZone.getTimeZone(ZoneOffset.UTC));

then reading an Instant from a database of column type TIMESTAMP can be done like this:

Instant myInstant = resultSet.getTimestamp(colIdx, UTC_CAL).toInstant();

while writing can be done like this:

preparedStatement.setTimestamp(colIdx, Timestamp.from(myInstant), UTC_CAL);

It should be noted that the above methodology is "safe", meaning it will work consistently regardless of the database server's setting for default timezone or your own JVM's timezone setting.

ORM support

As you can imagine, those ORMs which indeed supports Instant behind the scenes do exactly as above. Hibernate and Nucleus support Instant, EclipseLink not yet.

Your entity class should simply look like this:

@Entity
public class MyEntity {
  ...
  @Column(name="created_at")
  private Instant createdAt;

  // getters and setters
}

When using Hibernate, you can find many tales on the Internet of having to set hibernate.jdbc.time_zone to UTC. This is not necessary with the above, at least not with Hibernate 6. The reason is that Hibernate can see your intent (you specify Instant, not any of the other JSR-310 types), so it knows it has to use a static UTC_CAL when reading or writing the values from/to the database.

Should you be using Instant in JPA code ?

As stated, Instant is not currently in the JPA Spec but seems to be finally coming up. There are two reasons why I would gladly use it anyway:

  • Hibernate is Spring's default. Yes, you can probably use something else, but I bet very few do. So using something Hibernate specific doesn't bother me too much.
  • When Instant finally comes to the JPA Spec, I bet it will simply work just like Hibernate already do. So no change.
Cele answered 26/8, 2023 at 17:30 Comment(0)
A
2

JPA is now support java.time.Instant, as Jakarta Persistence has updated its standards to 3.2, even JDBC didn't support it yet:

Added support for java.time.Instant and java.time.Year and clarified JDBC mappings for basic types

In addition, the provider must support mapping:

  • java.time.Instant to the JDBC TIMESTAMP or TIMESTAMP_WITH_TIMEZONE type,

See also:

https://jakartaee.github.io/persistence/latest/draft.html#a486 https://github.com/jakartaee/persistence/issues/163

Apocarpous answered 6/2, 2024 at 18:49 Comment(0)
B
0

java.time.Instant is not supported by JPA may be because it let's you choose how to store it :

  • as some sort of datetime using a converter to java.sql.Timestamp or to java.time.OffsetDateTime
  • as long with seconds precision
  • as long with milliseconds precision.

My choice is to store it as a long as you know exactly what it means.

Biased answered 17/12, 2023 at 17:27 Comment(0)
W
-3

It's not a JPA issue but a JDBC issue.

JDBC supports Date, Timestamp, LocalDate, LocalTime and LocalDateTime but NOT Instant.

This is not a Java issue but an SQL issue whereby what is stored in the database is year-month-day-hour-minute-second construct.

Think of SQL functions : YEAR() MONTH() etc...

These functions cannot be applied to simple millisencods since 1970 number. They do require a LocalDateTime construct to perform these functions.

Wafture answered 18/3, 2018 at 13:26 Comment(4)
This Answer is entirely incorrect. (A) JDBC 4.2 and later supports java.time: See the methods PreparedStatement::setObject and ResultSet::getObject. This support includes Instant. (B) The LocalDateTime is exactly the wrong class, as it lacks any concept of time zone or offset-from-UTC. So a LocalDateTime does not represent a moment, is not a point on the timeline, and cannot be used to represent a number that is a count from the 1970-01-01T00:00Z epoch reference.Liew
(C) It is a JPA problem. Hibernate already supports java.time including Instant. From my limited reading (I’m not a user), it seems to be a problem with the people responsible for JPA being lazy or not competent.Liew
@Basil Bourque There are people who thinks so (about laziness or incompetence), but in this case the simplest way is to implement Instant - which is a TIMESTAMP and do not waste time with LocalDate, LocalTime etc.Aurignacian
@Aurignacian Yes, as a workaround, you may need to use java.sql.Timestamp. But immediately convert to java.time classes by calling on new conversion methods added to the old legacy date-time classes: Instant instant = myJavaSqlTimestamp.toInstant() ;. Those legacy date-time classes are a wretched mess of poor design and hacks – avoid/minimize their usage. The java.time framework was built to replace those old classes for many good reasons.Liew

© 2022 - 2025 — McMap. All rights reserved.