What’s new in JPA 2.2 – Java 8 Date and Time Types

(Last Updated On: January 15, 2018)

Introduction

Now that the JPA 2.2 Review Ballot was approved let’s start analyzing some of the new additions to the standard which have been supported by Hibernate for quite some time already. In this article, we are going to see how JPA 2.2 Date/Time works, and which types you need to use depending on your business case requirements.

Java 8 Date/Time support

The JPA 2.2 change log says that only the following types are going to be supported:

  • java.time.LocalDate
  • java.time.LocalTime
  • java.time.LocalDateTime
  • java.time.OffsetTime
  • java.time.OffsetDateTime

While LocalDateTime is rather straightforward since it only captures a point in time, similar to java.util.Date, OffsetDateTime is more problematic because it only captures the offset, but not the time zone rules such as DST (Daylight Saving Time) or other rules defined by ZoneId and typically supported by ZonedDateTime.

It’s also curious that the standard does not support the java.time.Duration type which can come in handy in many business use cases.

Domain Model

Considering we have following entities in our system:

The Employee entity contains the following Java 8 Date Time attributes:

  • birthday attribute is a LocalDate since we are only interested in the Date part
  • updatedOn is a LocalDateTime since this attribute needs to store both Date and Time information
@Entity(name = "Employee")
public class Employee {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String name;

    private LocalDate birthday;

    @Column(name = "updated_on")
    private LocalDateTime updatedOn;

    //Getters are setters omitted for brevity
}

For more details about the @NaturalId annotation, check out this article.

The Meeting entity features the following Java 8 Date Time attributes:

  • startsAt is a ZoneDateTime which, even if not supported by JPA 2.2, is probably a more suitable version of OffsetDateTime
  • the duration attribute might not be supported by JPA 2.2, but we will use it since Hibernate supports all these types
@Entity(name = "Meeting")
public class Meeting {

    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "employee_id")
    private Employee createdBy;

    @Column(name = "starts_at")
    private ZonedDateTime startsAt;

    private Duration duration;

    //Getters are setters omitted for brevity
}

Testing time

Assuming we persist the following entities:

Employee employee = new Employee();
employee.setName( "Vlad Mihalcea" );
employee.setBirthday(
    LocalDate.of(
        1981, 12, 10
    )
);
employee.setUpdatedOn(
    LocalDateTime.of(
        2015, 12, 1,
        8, 0, 0
    )
);

entityManager.persist( employee );

Meeting meeting = new Meeting();
meeting.setId( 1L );
meeting.setCreatedBy( employee );
meeting.setStartsAt(
    ZonedDateTime.of(
        2017, 6, 25,
        11, 30, 0, 0,
        ZoneId.systemDefault()
    )
);
meeting.setDuration(
    Duration.of( 45, ChronoUnit.MINUTES )
);

entityManager.persist( meeting );

Hibernate is going to generate the following SQL statements:

INSERT INTO Employee (
    birthday, 
    name, 
    updated_on, 
    id
) 
VALUES (
    '1981-12-10', 
    'Vlad Mihalcea', 
    '2015-12-01 08:00:00.0', 
    1
)

INSERT INTO Meeting (
    employee_id, 
    duration, 
    starts_at, 
    id
) 
VALUES (
    1, 
    2700000000000, 
    '2017-06-25 11:30:00.0', 
    1
)

Both LocalDateTime and ZonedDateTime share the org.hibernate.type.descriptor.sql.TimestampTypeDescriptor, meaning that they are going to be persisted as java.sql.Timestamp.

While for LocalDateTime it makes no difference since, just like java.sql.Timestamp, it only captures a time snapshot, the ZonedDateTime is going to lose the zone information.

When loading back our entities:

Employee employee = entityManager
.unwrap( Session.class )
.bySimpleNaturalId( Employee.class )
.load( "Vlad Mihalcea" );

assertEquals(
    LocalDate.of(
        1981, 12, 10
    ),
    employee.getBirthday()
);
assertEquals(
    LocalDateTime.of(
        2015, 12, 1,
        8, 0, 0
    ),
    employee.getUpdatedOn()
);

Meeting meeting = entityManager.find( Meeting.class, 1L );
assertSame(
    employee, meeting.getCreatedBy()
);
assertEquals(
    ZonedDateTime.of(
        2017, 6, 25,
        11, 30, 0, 0,
        ZoneId.systemDefault()
    ),
    meeting.getStartsAt()
);
assertEquals(
    Duration.of( 45, ChronoUnit.MINUTES ),
    meeting.getDuration()
);

The original Java 8 Data Time objects match the ones being persisted.

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

The reason why ZonedDateTime equals the one we previously saved is because the underlying TIMESTAMP value was transposed in the current System Time Zone.

Until all JDBC Drivers will support TIMESTAMP WITH TIMEZONE at the java.sql.Statement parameter value binding level which is a requirement for HHH-11773, it’s doubtful that you really need to use ZonedDateTime or OffsetDateTime.

Currently, it’s much wiser to save all TIMESTAMP values in UTC, meaning that LocalDateTime is a much better fit for your JPA entities.

Subscribe to our Newsletter

* indicates required
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence, 
  • a 10% discount coupon for my book. 
Get the most out of your persistence layer!

Advertisements

15 thoughts on “What’s new in JPA 2.2 – Java 8 Date and Time Types

  1. I understand why it doesn’t support ZonedDateTime: there is no SQL type allowing to store a date, a time and a timezone. But since it supports java.util.Date, why doesn’t it support java.time.Instant, which is basically its modern equivalent (and in my experience, the most often wanted type to persist)?

    1. Makes sense what you said about ZonedDateTime. As for Instant, if the applications saves all timestamps in UTC, then the LocalDateTime can also capture the instant of time as it is observed at the UTC timezone. Hibernate supports saving Instant as well via InstantType.

  2. Hello Vlad,
    I couldn’t find the jpa2.2 on the mvn central repository. What should be the maven dependency for it ?
    Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *