Mastering Date Range Queries in Hibernate: A Step-by-Step Guide

By • min read

Introduction

Querying records between two dates is a cornerstone of enterprise applications. Whether you're pulling monthly invoices or scanning logs for a specific window, Hibernate offers versatile tools for temporal queries. This guide walks you through three approaches—HQL, Criteria API, and Native SQL—so you can pick the right one for your project. We'll also cover common pitfalls and best practices to keep your code robust and maintainable.

Mastering Date Range Queries in Hibernate: A Step-by-Step Guide
Source: www.baeldung.com

What You Need

Step 1: Define Your Entity with Date Fields

First, create an entity that includes a date attribute. For modern Hibernate, use LocalDateTime directly:

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String trackingNumber;
    private LocalDateTime creationDate;
    // getters and setters
}

If you're stuck with older java.util.Date, add the @Temporal annotation to specify precision:

@Temporal(TemporalType.TIMESTAMP)
private Date legacyCreationDate;

Step 2: Query with HQL Using the BETWEEN Keyword

The simplest way to filter a date range in HQL is with BETWEEN. It's inclusive on both ends:

String hql = "FROM Order o WHERE o.creationDate BETWEEN :startDate AND :endDate";
List<Order> orders = session.createQuery(hql, Order.class)
  .setParameter("startDate", startDate)
  .setParameter("endDate", endDate)
  .getResultList();

This works well when the boundaries represent exact moments. However, beware of a common pitfall when working with calendar days.

Step 2.1: The Midnight Trap

If you want all orders from January 31, 2024, and set endDate to 2024-01-31T00:00:00, BETWEEN will exclude orders placed at 10:30 AM that day because the query only includes timestamps up to that exact midnight. To capture the entire day, you would need to manually adjust the time to the last millisecond (23:59:59.999) – a fragile approach.

Step 3: Use Comparison Operators for a Half-Open Interval

A more robust solution is to create a half-open interval: inclusive on the start, exclusive on the end. Replace BETWEEN with >= and <:

String hql = "FROM Order o WHERE o.creationDate >= :startDate AND o.creationDate < :endDate";
List<Order> orders = session.createQuery(hql, Order.class)
  .setParameter("startDate", startDate)  // e.g., 2024-01-01T00:00:00
  .setParameter("endDate", endDate)      // e.g., 2024-02-01T00:00:00
  .getResultList();

To get all of January 2024, set startDate to January 1st and endDate to February 1st (midnight). Orders on January 31st at 11:59 PM will be included because they are less than February 1st. This pattern avoids manual time calculations and works perfectly for days, months, or years.

Step 4: Use the Criteria API for Type-Safe Queries

The Criteria API provides a programmatic, type-safe alternative. Here’s how to write the same half-open interval using CriteriaBuilder:

CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Order> query = cb.createQuery(Order.class);
Root<Order> root = query.from(Order.class);

query.select(root)
  .where(cb.and(
    cb.greaterThanOrEqualTo(root.get("creationDate"), startDate),
    cb.lessThan(root.get("creationDate"), endDate)
  ));

List<Order> orders = session.createQuery(query).getResultList();

This approach is especially useful when building dynamic queries – you can conditionally add or remove predicates without concatenating strings.

Mastering Date Range Queries in Hibernate: A Step-by-Step Guide
Source: www.baeldung.com

Step 4.1: Using between in Criteria (with caution)

The Criteria API also has a cb.between() method, but it carries the same inclusive-on-both-ends behavior as HQL’s BETWEEN. Only use it when you are certain that both endpoints represent the exact moments you want to include.

Step 5: Fall Back to Native SQL (if Needed)

When you need database-specific date functions (e.g., DATE_TRUNC in PostgreSQL), or performance-tuning with native query hints, use NativeQuery:

String sql = "SELECT * FROM orders WHERE creation_date >= :startDate AND creation_date < :endDate";
List<Order> orders = session.createNativeQuery(sql, Order.class)
  .setParameter("startDate", startDate)
  .setParameter("endDate", endDate)
  .getResultList();

Native SQL bypasses Hibernate's abstraction, so you lose portability. Reserve it for cases where HQL or Criteria cannot express the query efficiently.

Tips for Robust Date Range Queries

By following these steps, you'll write date range queries that are both correct and maintainable. Happy coding!

Recommended

Discover More

BYD's 1,000-HP Denza Z Hypercar Set to Challenge European Luxury This SummerFrom Repository to Roguelike: Your Step-by-Step Guide to Building a Codebase Dungeon with GitHub Copilot CLIAchieving Transparent Agentic AI: A Structured Approach to Identify Key Transparency MomentsConsciousness as the Foundation of Reality: A Step-by-Step Guide to the New ParadigmiRacing Connect Brings Immersive Mixed Reality Racing to Apple Vision Pro