Skip to content
Home Java HQL vs JPQL vs Native SQL

HQL vs JPQL vs Native SQL

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Hibernate & JPA → Topic 5 of 7
Master the differences between HQL, JPQL, and Native SQL in Spring Boot.
⚙️ Intermediate — basic Java knowledge assumed
In this tutorial, you'll learn
Master the differences between HQL, JPQL, and Native SQL in Spring Boot.
  • JPQL is a core concept in JPA that ensures your application remains database-agnostic and focused on the domain model.
  • HQL provides more power than JPQL, specifically for complex joins and Hibernate-specific optimizations, but couples your code to the provider.
  • Native SQL should be reserved for complex reporting, bulk operations, or leveraging modern vendor-specific features like PostgreSQL JSONB or GiST indexes.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer

Think of HQL, JPQL, and Native SQL as different ways to order pizza. JPQL is like using a standard international app that works everywhere (standardized). HQL is like using the specific features of a high-end delivery service (Hibernate-exclusive). Native SQL is like walking directly into the kitchen and speaking the chef's local dialect; it's the most powerful way to get exactly what you want, but it only works in that specific kitchen.

HQL, JPQL, and Native SQL are the three primary ways to retrieve data in a Spring Boot application using Hibernate. Choosing the right one determines your application's portability, performance, and maintainability. In modern enterprise environments, the goal is often a balance: 80% standardized abstraction for maintainability and 20% raw power for performance-critical bottlenecks.

In this guide, we'll break down exactly what each query type is, why they were designed to serve different layers of abstraction, and how to use them correctly in real projects. By the end, you'll have both the conceptual understanding and practical code examples to use HQL, JPQL, and Native SQL with confidence in any 'io.thecodeforge' project.

What Is HQL vs JPQL vs Native SQL and Why Does It Exist?

These query languages exist to bridge the gap between Java's object-oriented world and the relational database's table-oriented world. JPQL (Java Persistence Query Language) is a platform-independent language defined by the JPA specification that operates on entities rather than tables. HQL (Hibernate Query Language) is an extension of JPQL that offers advanced features like 'FETCH JOIN' or specific function support. Native SQL is raw SQL that bypasses the ORM abstraction to leverage database-specific features like Window Functions, CTEs (Common Table Expressions), or JSONB operations that standard JPQL cannot yet express.

By leveraging JPQL/HQL for the majority of our work, we ensure 'io.thecodeforge' applications remain portable. However, we reach for Native SQL when the abstraction becomes a cage, allowing us to squeeze every drop of performance out of engines like PostgreSQL or MySQL.

io/thecodeforge/repository/QueryComparison.java · JAVA
12345678910111213141516171819202122232425262728293031323334
package io.thecodeforge.repository;

import io.thecodeforge.model.Project;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
import java.util.List;

public class ForgeQueryService {

    @PersistenceContext
    private EntityManager entityManager;

    public void runQueries() {
        // 1. JPQL: Standardized, entity-based. Highly portable.
        List<Project> jpql = entityManager.createQuery(
            "SELECT p FROM Project p WHERE p.status = 'ACTIVE'", Project.class)
            .getResultList();

        // 2. HQL: Hibernate-specific extensions (like specific JOIN FETCH logic)
        // Note: In modern JPA, many HQL features are now standard JPQL.
        List<Project> hql = entityManager.createQuery(
            "from Project p left join fetch p.tasks where p.id = :id", Project.class)
            .setParameter("id", 1L)
            .getResultList();

        // 3. Native SQL: Production-grade PostgreSQL specific example (JSONB query)
        // io.thecodeforge: Using Native SQL for complex DB-specific types
        Query nativeQuery = entityManager.createNativeQuery(
            "SELECT * FROM forge_projects p WHERE p.metadata->>'priority' = 'HIGH'", 
            Project.class);
        List<Project> nativeResults = nativeQuery.getResultList();
    }
}
▶ Output
JPQL/HQL: select p0_.id... from forge_projects p0_ | Native: SELECT * FROM forge_projects p...
💡Key Insight:
The most important thing to understand is the level of abstraction. JPQL/HQL allows you to swap databases (PostgreSQL to Oracle) without changing code. Native SQL locks you into a specific vendor but unlocks 100% of their performance optimizations.

Common Mistakes and How to Avoid Them

When learning these query types, most developers hit the same set of gotchas—specifically SQL injection vulnerabilities and the loss of the 'N+1' protection provided by the L2 cache. A common mistake is using Native SQL for simple CRUD operations, which prevents Hibernate from performing automatic dirty checking on the results. Conversely, many try to force complex reporting logic into JPQL, resulting in inefficient queries that would be better handled by a fine-tuned Native SQL statement. At 'io.thecodeforge', we emphasize that Native SQL should be the 'last resort' for reads, but the 'first choice' for high-performance bulk updates and deletes.

io/thecodeforge/security/QuerySafety.java · JAVA
12345678910111213141516171819202122232425262728293031
package io.thecodeforge.security;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import java.util.List;

public class QuerySafety {
    
    // ANTI-PATTERN: Vulnerable to SQL Injection via string concatenation
    public void badQuery(EntityManager em, String input) {
        em.createNativeQuery("SELECT * FROM forge_users WHERE name = '" + input + "'").getResultList();
    }

    /**
     * CORRECT: io.thecodeforge best practice - Use Named Parameters
     * This prevents SQLi and allows the DB to cache the execution plan.
     */
    public void goodQuery(EntityManager em, String input) {
        List<?> users = em.createNativeQuery("SELECT * FROM forge_users WHERE name = :userName")
          .setParameter("userName", input)
          .getResultList();
    }

    /**
     * PRO TIP: Native SQL for bulk deletes to bypass session overhead
     */
    public int bulkDeleteOldLogs(EntityManager em) {
        return em.createNativeQuery("DELETE FROM forge_logs WHERE created_at < NOW() - INTERVAL '30 days'")
                 .executeUpdate();
    }
}
▶ Output
// Logs: Executing query with bound parameter: :userName -> [input]
// Logs: Native DELETE executed (Bypassing Persistence Context)
⚠ Watch Out:
Native SQL queries do not automatically return managed Entities. If you select raw columns, Hibernate won't track changes to those objects. Always use 'addEntity()' or 'resultClass' if you need the persistence context to track updates.
FeatureJPQLHQLNative SQL
TargetJava Entities (@Entity)Java Entities (@Entity)Database Tables/Views
PortabilityHigh (Provider Independent)Medium (Hibernate Only)Low (Database Specific)
Case SensitivityCase-sensitive (Entities/Fields)Case-sensitive (Entities/Fields)Depends on DB Collation
StandardJPA SpecificationHibernate ProprietaryANSI SQL / Vendor Dialect
OptimizationLimited by ORM logicHibernate optimizationsFull DB-level tuning
Type SafetyStrong (checked against Entity)Strong (checked against Entity)Weak (ResultSets/Object arrays)

🎯 Key Takeaways

  • JPQL is a core concept in JPA that ensures your application remains database-agnostic and focused on the domain model.
  • HQL provides more power than JPQL, specifically for complex joins and Hibernate-specific optimizations, but couples your code to the provider.
  • Native SQL should be reserved for complex reporting, bulk operations, or leveraging modern vendor-specific features like PostgreSQL JSONB or GiST indexes.
  • Always use named parameters (:param) to prevent SQL injection and allow for database execution plan reuse across all query types.
  • Remember that Native SQL bypasses the Persistence Context (L1 Cache) unless you specifically map the result back to an Entity.

⚠ Common Mistakes to Avoid

    Using Native SQL for simple updates. This bypasses Hibernate’s First-Level Cache, potentially causing stale data if you access the same entity later in the same transaction. Always flush the session before running native updates.

    ve updates.

    Hardcoding table names in JPQL. JPQL works with Entity names (e.g., 'Project'), not table names (e.g., 'forge_projects'). Using table names in JPQL will cause a 'QuerySyntaxException: [Table] is not mapped'.

    ot mapped'.

    Ignoring pagination in Native SQL. Native SQL pagination is not automatic; you must write the specific OFFSET/FETCH or LIMIT syntax for your specific database dialect or handle it manually.

    t manually.

    Using SELECT * in JPQL. While Hibernate might allow it, JPQL expects an alias like 'SELECT p FROM Project p'. Using '*' is a sign of Native SQL thinking applied to JPQL.

    ed to JPQL.

Interview Questions on This Topic

  • QExplain the difference between JPQL and HQL. Why would a developer choose one over the other today?
  • QWhen should you use a Native Query instead of JPQL in a production environment? Provide specific examples (e.g., CTEs, Window Functions).
  • QHow do you handle result mapping in Native SQL when you aren't returning a full Entity? Explain @SqlResultSetMapping.
  • QDoes JPQL support all SQL features like 'UNION' or 'INTERSECT'? How does Hibernate handle these if the JPA standard doesn't?
  • QWhat are the performance implications of using Native SQL updates versus Entity-based updates in a large batch process?
🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousOne-to-Many and Many-to-Many in HibernateNext →Hibernate Caching — First and Second Level
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged