Hibernate Query Language (HQL) Basics
Learn HQL — Hibernate's object-oriented query language that looks like SQL but queries entities and fields instead of tables and columns.
Introduction
session.get() is perfect when you already know a primary key, but real applications need to search: "find all students with marks above 80," or "find a student by email." For that, Hibernate gives you HQL — Hibernate Query Language. HQL looks almost exactly like SQL, with one crucial difference: it queries your Java classes and their fields, not your database tables and columns.
- How HQL differs conceptually from plain SQL
- How to write and execute a basic HQL query
- How to bind parameters safely with named and positional placeholders
- How to use WHERE, ORDER BY, and aggregate functions in HQL
HQL vs SQL
The single biggest mental shift when learning HQL is this: you write it against your domain model, not your schema. If your entity class is named Student with a field marks, your query references Student and marks — even if the underlying table is named tbl_students and the column is stu_marks.
| Aspect | SQL | HQL |
|---|---|---|
| Targets | Tables and columns | Entity classes and fields |
| Case sensitivity | Table/column names usually case-insensitive | Entity and field names are case-sensitive (Java identifiers) |
| SELECT * | Common | Not needed — "FROM Student" returns full Student objects |
| Joins | Written against foreign keys manually | Written against object references/associations |
| Portability | Tied to a specific database dialect for advanced syntax | Database-agnostic — Hibernate translates it for you |
When writing HQL, mentally replace "table" with "class" and "column" with "field." FROM Student WHERE marks > 80 reads almost like English once you make that switch.
Writing Your First HQL Query
HQL queries are created from a Session using createQuery(), and executed with list() (or getResultList() when using the JPA-style Query interface).
Session session = sessionFactory.openSession();
Query<Student> query = session.createQuery( "FROM Student", Student.class);
List<Student> students = query.list();
for (Student s : students) { System.out.println(s.getName() + " - " + s.getEmail());}
session.close();Click Run to see what this code prints.
Notice that "FROM Student" has no table name and no asterisk — Student is the entity class, and Hibernate already knows to fetch every mapped field.
Using Parameters
Never concatenate user input directly into an HQL string — that opens the door to injection attacks, just like raw SQL. Instead, use named parameters (prefixed with a colon) or positional parameters (question marks).
Query<Student> query = session.createQuery( "FROM Student WHERE email = :email", Student.class);query.setParameter("email", "aditi@example.com");
Student student = query.uniqueResult();System.out.println(student.getName());Query<Student> query = session.createQuery( "FROM Student WHERE name = ?1", Student.class);query.setParameter(1, "Rohan Verma");
Student student = query.uniqueResult();uniqueResult() throws NonUniqueResultException if more than one row matches. Use list() and take the first element, or add a more specific WHERE clause, when more than one match is possible.
WHERE, ORDER BY, and Aggregates
HQL supports the same clauses you already know from SQL — WHERE, ORDER BY, GROUP BY, and aggregate functions like COUNT, AVG, MAX, and MIN — all written against entity field names.
// Filter and sortQuery<Student> topStudents = session.createQuery( "FROM Student s WHERE s.marks > :minMarks ORDER BY s.marks DESC", Student.class);topStudents.setParameter("minMarks", 75.0);List<Student> results = topStudents.list();
// Aggregate queryQuery<Long> countQuery = session.createQuery( "SELECT COUNT(s) FROM Student s WHERE s.marks > :minMarks", Long.class);countQuery.setParameter("minMarks", 75.0);Long total = countQuery.uniqueResult();
System.out.println("Students above threshold: " + total);Click Run to see what this code prints.
Common Mistakes
- Writing the actual table/column names instead of entity/field names — HQL will throw a QuerySyntaxException.
- Concatenating user input into the query string instead of using parameters.
- Calling uniqueResult() on a query that can legitimately return multiple rows.
- Forgetting that HQL field names are case-sensitive because they mirror Java field names.
Best Practices
- Always bind values with named or positional parameters, never string concatenation.
- Use an alias (FROM Student s) once queries grow beyond a single entity, especially before joins.
- Prefer list() over uniqueResult() unless you are certain the query returns at most one row.
- Keep HQL close to the repository/DAO layer rather than scattering raw query strings across the codebase.
Frequently Asked Questions
Entity and field names are case-sensitive because they map directly to Java class and field names. HQL keywords like FROM, WHERE, and SELECT are not case-sensitive.
Yes — HQL supports bulk UPDATE and DELETE statements executed with executeUpdate(), which operate directly on the database without loading entities into memory first.
They are extremely similar. JPQL (Java Persistence Query Language) is the JPA standard, and HQL is Hibernate's implementation, which is a superset of JPQL with a few extra Hibernate-specific features.
Key Takeaways
- HQL queries entity classes and fields, not database tables and columns.
- createQuery() builds a Query object; list() or uniqueResult() executes it.
- Always use named (:name) or positional (?1) parameters to bind values safely.
- HQL supports WHERE, ORDER BY, GROUP BY, and aggregate functions just like SQL.
Summary
HQL gives you SQL-like expressiveness while staying anchored to your object model, which keeps queries portable across databases. Next, you will look at the Criteria API — a fully programmatic, type-safe alternative to writing query strings by hand.