Cannot save Instant Max or Min in Database.

Description

Testcase:
https://github.com/JavaTesting1/hibernate-test-case-templates/tree/InstantBug
Especially:
https://github.com/JavaTesting1/hibernate-test-case-templates/blob/InstantBug/orm/hibernate-orm-5/src/test/java/org/hibernate/bugs/JPAUnitTestCase.java

When trying to save Instant.MAX or Instant.MIN in database via entitymanager.persist the following errors occurr (ps my pc is in the german time zone):

For Instant.MAX:
java.time.DateTimeException: Invalid value for Year (valid values -999999999 - 999999999): 1000000001
at java.time.temporal.ValueRange.checkValidIntValue(ValueRange.java:330)
at java.time.temporal.ChronoField.checkValidIntValue(ChronoField.java:722)
at java.time.LocalDate.ofEpochDay(LocalDate.java:341)
at java.time.zone.ZoneRules.findYear(ZoneRules.java:928)
at java.time.zone.ZoneRules.getOffset(ZoneRules.java:487)
at java.time.ZonedDateTime.create(ZonedDateTime.java:455)
at java.time.ZonedDateTime.ofInstant(ZonedDateTime.java:409)
at java.time.Instant.atZone(Instant.java:1213)
at org.hibernate.type.descriptor.java.InstantJavaDescriptor.unwrap(InstantJavaDescriptor.java:70)
at org.hibernate.type.descriptor.java.InstantJavaDescriptor.unwrap(InstantJavaDescriptor.java:26)
at org.hibernate.type.descriptor.sql.TimestampTypeDescriptor$1.doBind(TimestampTypeDescriptor.java:48)
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:74)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:276)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:271)
at org.hibernate.type.AbstractSingleColumnStandardBasicType.nullSafeSet(AbstractSingleColumnStandardBasicType.java:39)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:2875)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:2850)
at org.hibernate.persister.entity.AbstractEntityPersister$4.bindValues(AbstractEntityPersister.java:3071)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:41)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3079)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3673)
at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81)
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:645)
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:282)
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263)
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:317)
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:332)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:289)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:196)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:127)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:62)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:804)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:789)
at org.hibernate.bugs.JPAUnitTestCase.testInstantMax(JPAUnitTestCase.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)

For Instant.MIN:
java.time.DateTimeException: Invalid value for Year (valid values -999999999 - 999999999): -1000000000
at java.time.temporal.ValueRange.checkValidIntValue(ValueRange.java:330)
at java.time.temporal.ChronoField.checkValidIntValue(ChronoField.java:722)
at java.time.LocalDate.ofEpochDay(LocalDate.java:341)
at java.time.LocalDateTime.ofEpochSecond(LocalDateTime.java:422)
at java.time.ZonedDateTime.create(ZonedDateTime.java:456)
at java.time.ZonedDateTime.ofInstant(ZonedDateTime.java:409)
at java.time.Instant.atZone(Instant.java:1213)
at org.hibernate.type.descriptor.java.InstantJavaDescriptor.unwrap(InstantJavaDescriptor.java:70)
at org.hibernate.type.descriptor.java.InstantJavaDescriptor.unwrap(InstantJavaDescriptor.java:26)
at org.hibernate.type.descriptor.sql.TimestampTypeDescriptor$1.doBind(TimestampTypeDescriptor.java:48)
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:74)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:276)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:271)
at org.hibernate.type.AbstractSingleColumnStandardBasicType.nullSafeSet(AbstractSingleColumnStandardBasicType.java:39)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:2875)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:2850)
at org.hibernate.persister.entity.AbstractEntityPersister$4.bindValues(AbstractEntityPersister.java:3071)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:41)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3079)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3673)
at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81)
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:645)
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:282)
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263)
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:317)
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:332)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:289)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:196)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:127)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:62)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:804)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:789)
at org.hibernate.bugs.JPAUnitTestCase.testInstantMin(JPAUnitTestCase.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)

Activity

Show:

Yoann RodièreJanuary 15, 2021 at 7:49 AM

Thanks for your answer, please keep us updated.

I think the way forward is indeed to modify applications so that they only try to store values that fall in the range of values accepted by the database. That will certainly be between LocalDateTime.MIN and LocalDateTime.MAX after conversion to the local timezone, which means ORM won’t throw an exception.

Maybe we could avoid the exception in Hibernate ORM, but even then, such extreme values would not be stored properly. I doubt many databases allow storing such extreme values as timestamps; from what I can see, they either raise an error (when you’re lucky) or silently use the closest value in the supported range.

In fact, if I was to change anything in Hibernate ORM, I would rather throw an exception in more cases, when we know values cannot be stored by the database. But I believe that’s the job of the JDBC driver, and would require passing the Instant to the driver directly, instead of transforming it to a Timestamp. Which is a major change better suited for ORM 6.

Timm KorteJanuary 14, 2021 at 12:55 PM

After talking to the developers: They are not sure what they are getting back - as they used Instant.MAX as a representation for “never expire” - and the cleanup code actually just looked for entries before a certain Instant (mostly “now”).
So now the code is changed to use Instant.ofEpochMilli(Long.MAX_VALUE) (+292278994-08-17T07:12:55.807Z - far enough to be “never” in the future and not fail on timezones).
Testing with existing datasets that have been stored with “Instant.MAX” will show if this is a valid path for us.

Yoann RodièreJanuary 13, 2021 at 9:55 AM

> what would be the effect when trying to retrieve entries from the database which were saved with Instant.MAX by a 5.3 version?

As far as I can see, it would correctly return Instant.MAX. The problem is with intermediary representations of the value when serializing, but deserialization code seems fine… at least for Instant.MAX. It will probably fail to deserialize Instant.MIN, though (same problem as serialization).

So, actually, Instant.MAX and Instant.MIN are never serialized correctly, even in 5.4.0. Instant values are converted to Timestamp before they are passed to the JDBC driver, and Timestamp only supports dates/times ranging from 292278994-08-17 08:12:55.192 (BC) to 292278994-08-17 08:12:55.807 (AD), as far as I can tell. So they cannot represent Instant.MIN and Instant.MAX.

Even if we solved that, I don’t know of any database that supports storing such extreme values.

So… How have you been using this exactly? Are you sure you’re getting back Instant.MAX/ Instant.MIN when loading your entities from the database?

Yoann RodièreJanuary 13, 2021 at 8:40 AM

I’m looking into it. Strangely, it seems Instant.MAX and Instant.MIN are outside of the range of values that can be represented through LocalDateTime, so the error only happens when converting the Instant to a LocalDateTime (we need the year/month/day/etc.) before we finally convert it to a Timestamp.

what would be the effect when trying to retrieve entries from the database which were saved with Instant.MAX by a 5.3 version?

As far as I can see, it would correctly return Instant.MAX. The problem is with intermediary representations of the value when serializing, but deserialization code seems fine… at least for Instant.MAX. It will probably fail to deserialize Instant.MIN, though (same problem as serialization).

Yes, it’s a mess. Converting between java.time and Timestamp requires to handle loads of corner cases. Hopefully it’ll get easier when we can rely on java.time support in JDBC drivers and don’t need to perform a conversion anymore, but we cannot do that in 5.4 as it would mean dropping support for older JDBC drivers that were supported in 5.4.0.

Timm KorteJanuary 12, 2021 at 5:59 PM

@Yoann Rodière sorry for assigning - for some reason, I can’t mention here.

Details

Assignee

Reporter

Worked in

Affects versions

Priority

Created July 3, 2019 at 2:04 PM
Updated January 15, 2021 at 7:49 AM