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

By • min read

Introduction

Querying records between two specific dates is a routine task in many enterprise applications—whether you're generating monthly reports, filtering user activity logs, or retrieving orders within a billing cycle. Hibernate, as a popular JPA implementation, offers multiple ways to perform these temporal queries: HQL, the Criteria API, and native SQL. This step-by-step guide walks you through each approach, highlighting common pitfalls and best practices to ensure accurate results.

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

What You Need

Step-by-Step Instructions

Step 1: Prepare Your Entity Class for Date Fields

Start by defining an entity that holds a date-time field. For modern Hibernate (5+), you can directly use Java 8's LocalDateTime without extra annotations. For example, an Order entity:

@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 still using legacy java.util.Date, you must annotate it with @Temporal(TemporalType.TIMESTAMP) to specify the date+time storage type.

Step 2: Understand the BETWEEN Operator Pitfall

The BETWEEN operator in HQL is inclusive on both ends. When using LocalDateTime, a query like WHERE o.creationDate BETWEEN :start AND :end includes records whose timestamps are exactly equal to :start or :end. But if :end is set to 2024-01-31T00:00:00, any order placed after that midnight—say at 10:30 AM on Jan 31—will be excluded. To capture an entire day, you would need to set the time to 23:59:59.999, which is fragile. A safer alternative is the half-open interval pattern.

Step 3: Write a Date Range Query with HQL Using BETWEEN

If your use case truly requires inclusive bounds and you control the endpoint timestamps precisely, you can still use BETWEEN. Example:

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 fine when endDate is already set to the last millisecond of the day. However, for most day-based filters, proceed to Step 4.

Step 4: Write a Robust HQL Query Using Comparison Operators (Half-Open Interval)

The most reliable pattern for querying full calendar days or months is the half-open interval: inclusive on start (>=) and exclusive on end (<). This avoids time-of-day miscalculations. For example, to get all orders from January 2024:

String hql = "FROM Order o WHERE o.creationDate >= :startDate AND o.creationDate < :endDate";
LocalDateTime start = LocalDateTime.of(2024, 1, 1, 0, 0);
LocalDateTime end = LocalDateTime.of(2024, 2, 1, 0, 0);
List<Order> orders = session.createQuery(hql, Order.class)
  .setParameter("startDate", start)
  .setParameter("endDate", end)
  .getResultList();

This captures every order from Jan 1 00:00:00 up to (but not including) Feb 1 00:00:00, effectively covering the entire month of January. You can apply this pattern for any boundary (days, weeks, years).

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

Step 5: Query Dates with the Criteria API

The Criteria API offers a type-safe alternative. Use cb.between() or cb.greaterThanOrEqualTo() with cb.lessThan(). Example for the half-open interval:

CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
Root<Order> root = cq.from(Order.class);
Predicate startPred = cb.greaterThanOrEqualTo(root.get("creationDate"), start);
Predicate endPred = cb.lessThan(root.get("creationDate"), end);
cq.where(startPred, endPred);
List<Order> orders = session.createQuery(cq).getResultList();

The same half-open principle applies here.

Step 6: Fallback to Native SQL for Complex Database-Specific Functions

Sometimes you need database-specific date functions (e.g., DATE() to truncate time). Use native SQL with createNativeQuery():

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

Be aware that this ties your code to a specific database dialect.

Tips for Success

Recommended

Discover More

Netflix's Party Game Success: How Boggle Became a Living Room Spectator SportHow to Reclaim Unconstitutional Tariff Duties and Reinvest in U.S. Manufacturing: A Blueprint from AppleReact Native 0.84 Launches with Hermes V1 as Default Engine, Promises Major Performance BoostLessons from the Snowden Leaks: An Exclusive Q&A with Former NSA Chief Chris InglisFrom Cheesy Escapes to Dancing Rats: Inside Wheeljam's Persuasion Wheel Madness