Hibernate / Hibernate 7 Basics Interview Questions
1. What is Hibernate 7 and when was it released?
Hibernate ORM 7 is the current major version of Hibernate, the most widely used Java ORM framework. Hibernate 7.0 was released on May 20, 2025. The current latest stable release is 7.4 (mid-2026).
- First production-ready release entirely under Apache Software License v2 (including Envers)
- Complete implementation of Jakarta Persistence 3.2
- Complete implementation of Jakarta Data 1.0
- Full support for Jakarta EE 11
| Property | Detail |
|---|---|
| GA release | May 20, 2025 |
| Latest stable | 7.4 (mid-2026) |
| License | Apache Software License v2 |
| Jakarta Persistence | 3.2 |
| Jakarta Data | 1.0 |
| Minimum Java | Java 17 |
| Managed in Spring Boot | Spring Boot 4.x |
2. What is ORM and why is Hibernate used instead of writing raw JDBC?
ORM (Object-Relational Mapping) automatically maps Java objects to relational database tables, eliminating JDBC boilerplate. Hibernate is the most widely used Java ORM.
| Task | Raw JDBC | Hibernate |
|---|---|---|
| Query | Write SQL, iterate ResultSet, map columns | session.find(Order.class, id) or JPQL |
| Insert | PreparedStatement + executeUpdate() | session.persist(order) |
| Update | Write UPDATE SQL | Modify managed entity - Hibernate auto-detects |
| Relationships | Manual JOIN queries | @OneToMany, @ManyToOne - automatic |
| Caching | None | First-level + optional second-level cache |
| Portability | DB-specific SQL | Dialect abstracts DB differences |
// Raw JDBC (boilerplate): try (Connection c = ds.getConnection(); PreparedStatement ps = c.prepareStatement("SELECT * FROM products WHERE id=?")) { ps.setLong(1, id); ResultSet rs = ps.executeQuery(); // manually map columns to fields... } // Hibernate 7 (clean): try (Session s = sf.openSession()) { return s.find(Product.class, id); }
3. How do you configure Hibernate 7 with Maven?
Hibernate 7 can be used standalone or as the JPA provider in Spring Boot 4. Key dependencies:
<!-- Standalone --> <dependency> <groupId>org.hibernate.orm</groupId> <artifactId>hibernate-core</artifactId> <version>7.4.0.Final</version> </dependency> <!-- Spring Boot 4 (managed version) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <!-- Includes hibernate-core 7.x transitively --> </dependency> <!-- Annotation processor for static metamodel --> <dependency> <groupId>org.hibernate.orm</groupId> <artifactId>hibernate-processor</artifactId> <version>7.4.0.Final</version> <scope>provided</scope> </dependency>
4. What is an entity in Hibernate 7 and how do you define one?
An entity is a Java class mapped to a database table. Each instance corresponds to a row. In Hibernate 7, entity classes follow Jakarta Persistence 3.2 rules.
import jakarta.persistence.*; @Entity @Table(name = "products") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "product_name", nullable = false, length = 200) private String name; @Column(precision = 10, scale = 2) private BigDecimal price; @Enumerated(EnumType.STRING) // store as 'ACTIVE' not 0/1 private ProductStatus status; protected Product() {} // JPA proxy constructor public Product(String name, BigDecimal price) { this.name = name; this.price = price; this.status = ProductStatus.ACTIVE; } }
5. What are entity states in Hibernate 7 and how do they transition?
Every entity instance in Hibernate exists in one of three states relative to a Session. Hibernate 7 enforces stricter rules on detached entities.
| State | Description | In session? | DB record? |
|---|---|---|---|
| Transient | Never associated with a Session | No | No |
| Persistent | Associated with open Session; changes tracked | Yes | Yes |
| Detached | Was persistent; Session closed | No | Yes |
// TRANSIENT Product p = new Product("Laptop", new BigDecimal("999")); // TRANSIENT -> PERSISTENT session.persist(p); // INSERT scheduled // Auto dirty checking: change field -> UPDATE at flush p.setPrice(new BigDecimal("899")); session.getTransaction().commit(); // flush: UPDATE sent // PERSISTENT -> DETACHED (session closes) // p is detached: changes NOT tracked // Hibernate 7: ONLY merge() re-integrates detached entities Product managed = session2.merge(p); // copies detached state managed.setPrice(new BigDecimal("799"));
6. What is the biggest breaking change in Hibernate 7: detached entity reassociation removal?
The most impactful breaking change in Hibernate 7 is the complete removal of detached entity reassociation. Methods like session.update(), session.saveOrUpdate(), and session.lock() on detached entities are removed.
| Removed method | Replacement |
|---|---|
| session.update(entity) | session.merge(entity) |
| session.saveOrUpdate(entity) | session.merge(entity) |
| session.lock(entity, LockMode) | Load fresh: session.find(Class, id, LockMode) |
| session.refresh(detached) | Only allowed on managed entities |
| hibernate.allow_refresh_detached_entity property | Removed entirely |
// Hibernate 6 (worked but deprecated): session.update(detachedProduct); // REMOVED in Hibernate 7! // Hibernate 7 - use merge(): Product managed = session.merge(detachedProduct); managed.setPrice(new BigDecimal("50")); // modify managed copy session.getTransaction().commit(); // Adding detached child to managed parent: // WRONG in Hibernate 7: parent.addChild(detachedChild); // throws EntityExistsException at flush! // CORRECT: Child managedChild = session.merge(detachedChild); parent.addChild(managedChild);
7. What is the Session in Hibernate 7 and what are its core CRUD operations?
The Session is Hibernate's primary interface for database operations. It represents a unit of work and maintains the first-level cache. Key API changes in Hibernate 7:
| Old method (removed) | Hibernate 7 replacement |
|---|---|
| session.save(entity) | session.persist(entity) |
| session.get(Class,id) | session.find(Class,id) |
| session.delete(entity) | session.remove(entity) |
| session.update(entity) | session.merge(entity) |
| session.saveOrUpdate(entity) | session.merge(entity) |
session.beginTransaction(); // CREATE Product p = new Product("Book", new BigDecimal("19.99")); session.persist(p); // replaces save() // READ: returns null if not found Product found = session.find(Product.class, 1L); // replaces get() // READ lazy proxy Product ref = session.getReference(Product.class, 1L); // UPDATE: automatic dirty checking - no explicit call needed found.setPrice(new BigDecimal("24.99")); // DELETE session.remove(found); // replaces delete() // MERGE: integrate detached state Product copy = session.merge(detached); session.getTransaction().commit();
8. What is the Hibernate SessionFactory and how do you create one?
The SessionFactory is the central, thread-safe, immutable configuration object. It is expensive to create and should be instantiated once per application lifecycle.
Configuration config = new Configuration(); config.setProperty("hibernate.connection.url", "jdbc:postgresql://localhost:5432/mydb"); config.setProperty("hibernate.connection.username", "user"); config.setProperty("hibernate.connection.password", "secret"); config.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"); config.setProperty("hibernate.hbm2ddl.auto", "validate"); config.setProperty("hibernate.show_sql", "true"); config.addAnnotatedClass(Product.class); config.addAnnotatedClass(Order.class); // Build once, reuse everywhere: SessionFactory sf = config.buildSessionFactory(); // Usage: try (Session session = sf.openSession()) { session.beginTransaction(); // operations session.getTransaction().commit(); }
9. What is HQL (Hibernate Query Language) in Hibernate 7?
HQL is Hibernate's object-oriented query language operating on entity objects rather than tables. Hibernate 7 adds new HQL functions including regexp, JSON/XML manipulation, and set-returning functions.
// Basic HQL - uses entity class name, not table name: List<Product> all = session .createQuery("FROM Product", Product.class) .getResultList(); // WHERE with named parameter: List<Product> active = session .createQuery("FROM Product WHERE status = :status", Product.class) .setParameter("status", ProductStatus.ACTIVE) .getResultList(); // Constructor expression (DTO projection): List<ProductSummary> s = session .createQuery( "SELECT new com.example.ProductSummary(p.id, p.name, p.price) " + "FROM Product p WHERE p.price < :max", ProductSummary.class) .setParameter("max", new BigDecimal("100")) .getResultList(); // JOIN on association fields: List<Order> orders = session .createQuery("FROM Order o JOIN o.customer c WHERE c.email = :email", Order.class) .setParameter("email", "alice@example.com") .getResultList(); // Pagination: List<Product> page = session .createQuery("FROM Product ORDER BY createdAt DESC", Product.class) .setFirstResult(0).setMaxResults(20).getResultList(); // Hibernate 7 new: regexp operator List<Product> matched = session .createQuery("FROM Product WHERE name regexp :pattern", Product.class) .setParameter("pattern", "^(Laptop|Phone)") .getResultList();
10. What is JPQL vs HQL in Hibernate 7?
JPQL is the standardised query language from Jakarta Persistence. HQL is a strict superset with additional Hibernate-specific features.
| Aspect | JPQL | HQL |
|---|---|---|
| Specification | Jakarta Persistence 3.2 standard | Hibernate superset of JPQL |
| Portability | Works with any JPA provider | Hibernate-specific |
| Functions | Standard functions | All JPQL + regexp, ilike, array, JSON, XML (Hibernate 7) |
| Set operations | UNION/INTERSECT/EXCEPT (JP 3.2) | Same as JPQL + more |
// JPQL (standard): EntityManager em = emf.createEntityManager(); List<Product> p = em .createQuery("SELECT p FROM Product p WHERE p.price < :max", Product.class) .setParameter("max", new BigDecimal("100")) .getResultList(); // HQL shorthand (Hibernate-specific): List<Product> p2 = session .createQuery("FROM Product WHERE price < :max", Product.class) .setParameter("max", new BigDecimal("100")) .getResultList(); // HQL-only: regexp List<Product> re = session .createQuery("FROM Product WHERE name regexp :pat", Product.class) .setParameter("pat", "^(Laptop|Phone)").getResultList(); // UNION (supported in both JPQL 3.2 and HQL): List<Object[]> combined = session.createQuery( "SELECT p.id, p.name FROM Product p WHERE p.category = :cat " + "UNION " + "SELECT s.id, s.name FROM Service s WHERE s.category = :cat", Object[].class).setParameter("cat", "tech").getResultList();
11. How does Hibernate 7 handle primary key generation strategies?
Primary key generation is controlled by @GeneratedValue. Hibernate 7 supports all four JPA strategies plus UUID as a first-class feature.
| Strategy | How it works | Best for |
|---|---|---|
| IDENTITY | DB auto-increment (SERIAL/AUTO_INCREMENT) | Simple setups; disables JDBC batch inserts |
| SEQUENCE | Uses DB sequence; Hibernate batches allocation | PostgreSQL, Oracle; supports batch inserts |
| TABLE | Simulates sequence with a special table | Portable but slow |
| AUTO | Hibernate picks best strategy | Quick prototyping |
| UUID | Generates UUID as PK | Distributed systems |
// IDENTITY: @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // SEQUENCE (recommended for batch inserts): @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq") @SequenceGenerator(name="product_seq", sequenceName="product_id_seq", allocationSize=50) private Long id; // UUID (first-class in Hibernate 7): @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; // Time-based UUID v7: @Id @UuidGenerator(style = UuidGenerator.Style.TIME) private UUID id;
12. What is the first-level cache (persistence context) in Hibernate 7?
The first-level cache is the per-Session in-memory cache for all entities loaded or persisted within an open Session. It is mandatory - every Session has its own first-level cache.
Session session = sf.openSession(); session.beginTransaction(); Product p1 = session.find(Product.class, 1L); // DB hit Product p2 = session.find(Product.class, 1L); // CACHE HIT - no SQL! Product p3 = session.find(Product.class, 1L); // CACHE HIT System.out.println(p1 == p2); // true! Same object reference // Evict specific entity: session.detach(p1); // p1 is now DETACHED Product p4 = session.find(Product.class, 1L); // DB hit again // Clear entire cache: session.clear(); // all entities become DETACHED // Bulk processing pattern (prevent OutOfMemoryError): for (int i = 0; i < 100_000; i++) { session.persist(products.get(i)); if (i % 50 == 0) { session.flush(); // write to DB session.clear(); // free cache memory } }
13. What is the second-level cache in Hibernate 7 and how does it work with StatelessSession?
The second-level cache is an optional cross-session application-wide cache. Hibernate 7 changes StatelessSession's behaviour: it now reads/writes the L2 cache by default (was bypassed in v6).
| Aspect | First-level cache | Second-level cache |
|---|---|---|
| Scope | Per Session | Per SessionFactory (application lifetime) |
| Mandatory | Yes | Optional |
| Shared across sessions | No | Yes |
| Provider | Built-in | Infinispan, Ehcache 3, Caffeine |
// Enable L2 cache on an entity: @Entity @Cacheable @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Country { @Id private Long id; private String name; } // Spring Boot 4 configuration: // spring.jpa.properties.hibernate.cache.use_second_level_cache=true // HIBERNATE 7: StatelessSession now uses L2 cache by default! // To bypass (for bulk processing): StatelessSession ss = sf.openStatelessSession(); ss.setCacheMode(CacheMode.IGNORE); // bypass L2 cache
14. What are lazy and eager fetching in Hibernate 7 and how do you avoid the N+1 problem?
Lazy fetching loads associations on demand. Eager fetching loads them immediately. Wrong strategy choice leads to the N+1 query problem.
// LAZY (default for @OneToMany): @OneToMany(mappedBy="customer", fetch=FetchType.LAZY) private List<Order> orders; // N+1 PROBLEM: // 1 query: SELECT * FROM customers // N queries: SELECT * FROM orders WHERE customer_id=1 ... customer_id=N List<Customer> customers = session.createQuery("FROM Customer", Customer.class).getResultList(); for (Customer c : customers) { c.getOrders().size(); // triggers N extra queries! } // SOLUTION 1: JOIN FETCH List<Customer> customers = session .createQuery("FROM Customer c JOIN FETCH c.orders", Customer.class) .getResultList(); // 1 query with JOIN // SOLUTION 2: @BatchSize @OneToMany(mappedBy="customer") @BatchSize(size=25) // loads 25 collections in one IN query private List<Order> orders; // SOLUTION 3: EntityGraph EntityGraph<Customer> graph = session.createEntityGraph(Customer.class); graph.addAttributeNode("orders"); List<Customer> list = session.createSelectionQuery("FROM Customer", Customer.class) .applyFetchGraph(graph).getResultList();
15. How do you map one-to-many and many-to-one relationships in Hibernate 7?
Relationships are mapped using @OneToMany, @ManyToOne, etc. The owning side holds the foreign key column.
@Entity public class Customer { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String name; @OneToMany( mappedBy="customer", cascade=CascadeType.ALL, orphanRemoval=true, fetch=FetchType.LAZY ) private List<Order> orders = new ArrayList<>(); public void addOrder(Order o) { orders.add(o); o.setCustomer(this); // keep both sides in sync! } } @Entity @Table(name="orders") public class Order { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private BigDecimal total; @ManyToOne(fetch=FetchType.LAZY) // explicit LAZY recommended @JoinColumn(name="customer_id", nullable=false) private Customer customer; // owning side: holds FK } // Usage: Customer c = new Customer("Alice"); c.addOrder(new Order(new BigDecimal("50"))); session.persist(c); // cascades to orders
16. What is the StatelessSession in Hibernate 7 and when should you use it?
StatelessSession has no first-level cache, no dirty checking, and no automatic state management. In Hibernate 7 it reaches near feature-parity with Session and is the preferred tool for bulk processing.
| Aspect | Session | StatelessSession |
|---|---|---|
| First-level cache | Yes | No |
| Dirty checking | Yes | No - must call update() explicitly |
| Memory | Can grow with large result sets | Minimal |
| L2 cache (Hibernate 7) | Yes | Yes (new - was bypassed in v6) |
| Best for | Transactional CRUD | Bulk ETL, batch imports, reporting |
try (StatelessSession ss = sf.openStatelessSession()) { ss.beginTransaction(); // Hibernate 7: must set batch size explicitly on StatelessSession! ss.setJdbcBatchSize(50); // hibernate.jdbc.batch_size has NO effect here // New batch operations in Hibernate 7: ss.insertMultiple(products); // batch inserts ss.updateMultiple(toUpdate); // batch updates // Bypass L2 cache for bulk operations (new default is to use it): ss.setCacheMode(CacheMode.IGNORE); // Scrollable results for very large datasets: ScrollableResults<Product> scroll = ss .createQuery("FROM Product", Product.class) .scroll(ScrollMode.FORWARD_ONLY); while (scroll.next()) { Product p = scroll.get(); p.setPrice(p.getPrice().multiply(new BigDecimal("0.9"))); ss.update(p); // explicit - no dirty checking } ss.getTransaction().commit(); }
17. What is the Criteria API in Hibernate 7 and what are SelectionSpecification and RestrictionSpecification?
Hibernate 7 introduces a new type-safe Criteria-like API: SelectionSpecification and RestrictionSpecification - simpler than the JPA Criteria API for common cases while remaining fully type-safe via the static metamodel.
// Static metamodel auto-generated by hibernate-processor: // public abstract class Product_ { // public static volatile SingularAttribute<Product, Long> id; // public static volatile SingularAttribute<Product, String> name; // public static volatile SingularAttribute<Product, BigDecimal> price; // } // Hibernate 7: NEW SelectionSpecification API var results = SelectionSpecification .create(Product.class) .restrict(Restriction.equal(Product_.status, ProductStatus.ACTIVE)) .restrict(Restriction.contains(Product_.name, "book", false)) .restrict(Restriction.greaterThan(Product_.price, new BigDecimal("10"))) .sort(Order.desc(Product_.price)) .createQuery(session) .setMaxResults(20) .getResultList(); // RestrictionSpecification: add restrictions to an EXISTING query var priceFilter = RestrictionSpecification .of(Product.class) .where(Restriction.between(Product_.price, new BigDecimal("10"), new BigDecimal("100"))); var update = session.createMutationQuery("UPDATE Product SET discount = :disc"); priceFilter.applyTo(update); update.setParameter("disc", new BigDecimal("5")).executeUpdate(); // Traditional JPA Criteria still works: CriteriaBuilder cb = session.getCriteriaBuilder(); CriteriaQuery<Product> cq = cb.createQuery(Product.class); Root<Product> root = cq.from(Product.class); cq.where(cb.equal(root.get(Product_.status), ProductStatus.ACTIVE)); List<Product> r = session.createQuery(cq).getResultList();
18. How does @Embeddable and @Embedded mapping work in Hibernate 7?
@Embeddable marks a class as a value type stored within the parent entity's table. Hibernate 7 adds @EmbeddedColumnNaming (incubating) to simplify prefix/suffix mapping for multiple embeddables of the same type.
@Embeddable public class Address { @Column(name="street") private String street; @Column(name="city") private String city; @Column(name="postcode", length=10) private String postcode; } @Entity public class Customer { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String name; @Embedded private Address shippingAddress; // columns: street, city, postcode // Hibernate 6 approach (verbose - one @AttributeOverride per field): @Embedded @AttributeOverrides({ @AttributeOverride(name="street", column=@Column(name="billing_street")), @AttributeOverride(name="city", column=@Column(name="billing_city")), @AttributeOverride(name="postcode", column=@Column(name="billing_postcode")) }) private Address billingAddress; // Hibernate 7 NEW (incubating): much simpler! // @Embedded // @EmbeddedColumnNaming("billing_{column_name}") // private Address billingAddress; // Generates: billing_street, billing_city, billing_postcode }
19. What are the new FindOptions in Jakarta Persistence 3.2 and Hibernate 7?
Jakarta Persistence 3.2 introduces FindOption, RefreshOption, and LockOption for fine-grained control over find/refresh/lock operations without separate methods for each combination. Hibernate 7 also adds session.findMultiple().
// Before JP 3.2: separate methods for different behaviours // After: varargs options on find() import jakarta.persistence.Timeouts; // Find with pessimistic lock and no-wait timeout: Book book = session.find( Book.class, isbn, LockModeType.PESSIMISTIC_WRITE, Timeouts.NO_WAIT // fail immediately if locked ); // Find bypassing second-level cache: Book book2 = session.find( Book.class, isbn, CacheRetrieveMode.BYPASS ); // session.findMultiple(): batch load multiple entities List<Book> books = session.findMultiple( Book.class, List.of(isbn1, isbn2, isbn3) ); // Loads all 3 in a single SELECT ... WHERE id IN (?,?,?) // RefreshOption: session.refresh(managedBook, LockModeType.PESSIMISTIC_READ); // LockOption: session.lock(managedBook, LockModeType.PESSIMISTIC_WRITE, Timeouts.of(5, TimeUnit.SECONDS));
20. What is optimistic locking in Hibernate 7 and how do you implement it?
Optimistic locking uses a version column to detect concurrent update conflicts at commit time instead of locking the row. Hibernate 7 throws OptimisticLockException when the version has changed since the entity was loaded.
@Entity public class Product { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String name; private BigDecimal price; @Version // Hibernate manages automatically private Long version; } // How it works: // Load: SELECT id, name, price, version FROM products WHERE id=1 // version=5 // Modify: product.setPrice(new BigDecimal("99")) // Flush: UPDATE products SET price=99, version=6 WHERE id=1 AND version=5 // If another TX updated it: version!=5 -> OptimisticLockException! // Handling conflicts: for (int attempt = 0; attempt < 3; attempt++) { try (Session session = sf.openSession()) { session.beginTransaction(); Product p = session.find(Product.class, productId); p.setPrice(p.getPrice().multiply(new BigDecimal("0.9"))); session.getTransaction().commit(); break; // success } catch (OptimisticLockException e) { if (attempt == 2) throw e; Thread.sleep(50 * (attempt + 1)); } }
21. What is pessimistic locking in Hibernate 7 and what are the improvements?
Pessimistic locking acquires a DB-level lock on a row when loaded. Hibernate 7 improves this with the Timeouts API from Jakarta Persistence 3.2.
// PESSIMISTIC_WRITE: SELECT ... FOR UPDATE Product p = session.find(Product.class, id, LockModeType.PESSIMISTIC_WRITE); // Hibernate 7: Timeouts API import jakarta.persistence.Timeouts; // Fail immediately if lock unavailable: Product p2 = session.find(Product.class, id, LockModeType.PESSIMISTIC_WRITE, Timeouts.NO_WAIT); // Wait up to 5 seconds: Product p3 = session.find(Product.class, id, LockModeType.PESSIMISTIC_WRITE, Timeouts.of(5, TimeUnit.SECONDS)); // Lock an already-managed entity: session.lock(managed, LockModeType.PESSIMISTIC_WRITE, Timeouts.NO_WAIT); // Lock modes: // PESSIMISTIC_READ -> SELECT FOR SHARE // PESSIMISTIC_WRITE -> SELECT FOR UPDATE
22. What is the @ManyToMany relationship in Hibernate 7 and how do you map it?
A many-to-many relationship requires a join table. Each entity can be associated with multiple instances of the other.
@Entity public class Student { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String name; @ManyToMany(fetch=FetchType.LAZY) @JoinTable( name="student_course", joinColumns=@JoinColumn(name="student_id"), inverseJoinColumns=@JoinColumn(name="course_id") ) private Set<Course> courses = new HashSet<>(); public void enrollIn(Course c) { courses.add(c); c.getStudents().add(this); // keep both sides in sync! } } @Entity public class Course { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String title; @ManyToMany(mappedBy="courses") // inverse side private Set<Student> students = new HashSet<>(); } // When you need extra columns on the join: use an explicit join entity! @Entity public class Enrollment { @Id @GeneratedValue private Long id; @ManyToOne @JoinColumn(name="student_id") private Student student; @ManyToOne @JoinColumn(name="course_id") private Course course; private LocalDate enrolledAt; // impossible with @ManyToMany private String grade; }
23. What is Hibernate's flush behaviour and how does it affect SQL execution?
Flushing synchronises the in-memory persistence context with the database by sending pending SQL. Flush does NOT commit the transaction.
| FlushMode | When SQL sent | Use case |
|---|---|---|
| AUTO (default) | Before queries and at commit | Normal transactional operations |
| COMMIT | Only at commit | Read-heavy operations |
| ALWAYS | Before every query | Strict consistency required |
| MANUAL | Only explicit session.flush() | Bulk operations |
// AUTO flush mode (default): Product p = new Product("Laptop", new BigDecimal("999")); session.persist(p); // INSERT scheduled, NOT sent yet // Query triggers flush in AUTO mode: Long count = session.createQuery("SELECT COUNT(p) FROM Product p", Long.class) .getSingleResult(); // INSERT flushed first so count is accurate // Explicit flush: session.flush(); // sends SQL but does NOT commit // Check for pending changes: session.isDirty(); // true if unflushed changes exist // SQL ordering at flush: INSERT -> UPDATE -> DELETE // Prevents FK constraint violations
24. What are named queries in Hibernate 7 and how are they type-safe with TypedQueryReference?
Named queries are pre-defined HQL/SQL queries. Hibernate 7 (Jakarta Persistence 3.2) adds TypedQueryReference for type-safe, compile-time references via the static metamodel.
@Entity @NamedQuery( name="Product.findByCategory", query="FROM Product p WHERE p.category = :category ORDER BY p.price ASC" ) public class Product { @Id private Long id; private String name; private String category; } // Traditional way (error-prone string reference): List<Product> p = session .createNamedQuery("Product.findByCategory", Product.class) .setParameter("category", "electronics") .getResultList(); // Typo in "Product.findByCategory" = runtime exception! // Jakarta Persistence 3.2 / Hibernate 7: // TypedQueryReference in static metamodel: // Product_: public static final TypedQueryReference<Product> QUERY_FIND_BY_CATEGORY; List<Product> p2 = session .createQuery(Product_.QUERY_FIND_BY_CATEGORY) // compile-time safe! .setParameter("category", "electronics") .getResultList(); // Renaming the named query = compile error, not runtime exception!
25. What is the @Inheritance mapping in Hibernate 7 and what strategies are available?
Three strategies map a Java inheritance hierarchy to database tables, each with different performance and schema trade-offs.
| Strategy | Schema | Pros | Cons |
|---|---|---|---|
| SINGLE_TABLE | One table for all subclasses | Simple; best query performance | Sparse NULLs; no NOT NULL on subtype columns |
| JOINED | One table per class | Normalised; subtype constraints possible | JOIN per query; slower polymorphic queries |
| TABLE_PER_CLASS | Independent table per concrete class | No JOINs for single-type queries | Expensive UNION for polymorphic queries |
// SINGLE_TABLE (most common): @Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="payment_type", discriminatorType=DiscriminatorType.STRING) public abstract class Payment { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private BigDecimal amount; } @Entity @DiscriminatorValue("CREDIT_CARD") public class CreditCardPayment extends Payment { private String cardNumber; } @Entity @DiscriminatorValue("BANK_TRANSFER") public class BankTransferPayment extends Payment { private String iban; } // JOINED: @Entity @Inheritance(strategy=InheritanceType.JOINED) public abstract class Vehicle { @Id @GeneratedValue private Long id; private String registration; } @Entity @Table(name="cars") public class Car extends Vehicle { private int doors; }
26. What is the @Filter and auto-enabled filters feature in Hibernate 7?
Hibernate Filters are parameterised WHERE clause fragments applied to a Session. Hibernate 7 adds auto-enabled filters that activate automatically without explicit session.enableFilter() calls.
@FilterDef( name="activeFilter", parameters=@ParamDef(name="isActive", type=Boolean.class) ) @Filter(name="activeFilter", condition="is_active = :isActive") @Entity public class Product { @Id private Long id; private String name; private boolean isActive; } // Enable for this session: session.enableFilter("activeFilter").setParameter("isActive", true); // All Product queries now silently include: AND is_active = true List<Product> active = session.createQuery("FROM Product", Product.class).getResultList(); session.disableFilter("activeFilter"); // HIBERNATE 7: auto-enabled filter for multi-tenancy @FilterDef( name="tenantFilter", parameters=@ParamDef(name="tenantId", type=Long.class), autoEnabled=true // no session.enableFilter() needed! ) @Filter(name="tenantFilter", condition="tenant_id = :tenantId") @Entity public class Order { ... } // FilterDefContributor SPI resolves tenantId from ThreadLocal automatically
27. What is key-based pagination in Hibernate 7 and how does it differ from offset pagination?
Traditional offset pagination (LIMIT N OFFSET M) gets slower on deep pages. Key-based pagination uses a WHERE clause on the last key value, making each page O(1) regardless of depth.
// OFFSET PAGINATION (gets slower at depth): List<Product> page1000 = session .createQuery("FROM Product ORDER BY id ASC", Product.class) .setFirstResult(19_980) // DB must scan and skip 19,980 rows! .setMaxResults(20) .getResultList(); // HIBERNATE 7: Key-based pagination (O(1) at any depth) import org.hibernate.query.KeyedPage; import org.hibernate.query.KeyedResultList; // First page: KeyedResultList<Product> page = session .createQuery("FROM Product", Product.class) .getKeyedResultList( KeyedPage.firstPage(20, Order.asc(Product_.id)) ); List<Product> products = page.getResultList(); // Next page (use key from previous result): if (page.getNextPage() != null) { KeyedResultList<Product> next = session .createQuery("FROM Product", Product.class) .getKeyedResultList(page.getNextPage()); } // Generated SQL: WHERE id > :lastId ORDER BY id ASC LIMIT 20
28. What is Jakarta Data 1.0 and how does it integrate with Hibernate 7?
Jakarta Data 1.0 is a new specification in Jakarta EE 11 providing a standardised type-safe repository model. Hibernate 7 provides a complete implementation via hibernate-repositories module.
import jakarta.data.repository.*; @Repository public interface ProductRepository extends CrudRepository<Product, Long> { // Method name derivation: List<Product> findByStatus(ProductStatus status); List<Product> findByPriceLessThan(BigDecimal maxPrice); long countByStatus(ProductStatus status); // @Find: type-safe field query @Find List<Product> findByCategory( @By(Product_.CATEGORY) String category, @OrderBy(Product_.PRICE) Sort.Direction dir ); // @Query: JPQL @Query("FROM Product WHERE status = :status AND price < :maxPrice") List<Product> findCheapActive( @Param("status") ProductStatus status, @Param("maxPrice") BigDecimal maxPrice ); // Key-based pagination: Page<Product> findByCategory(String category, PageRequest page); } // Usage - inject like any Spring bean: @Autowired ProductRepository repo; List<Product> active = repo.findByStatus(ProductStatus.ACTIVE);
29. What is the @NaturalId feature in Hibernate 7 and what is @NaturalIdClass?
A natural ID is a business-meaningful unique identifier distinct from the surrogate PK. Hibernate 7 adds @NaturalIdClass for composite natural IDs.
@Entity public class Book { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; // surrogate PK (internal) @NaturalId // business identifier (external) @Column(unique=true, nullable=false) private String isbn; private String title; } // Optimised lookup (uses L2 cache): Book book = session.byNaturalId(Book.class) .using("isbn", "978-0-13-468599-1").load(); // @NaturalIdClass: COMPOSITE natural IDs (NEW in Hibernate 7) public class EmployeeNaturalId { String companyCode; String employeeNumber; // equals() and hashCode() required } @Entity @NaturalIdClass(EmployeeNaturalId.class) // NEW in Hibernate 7 public class Employee { @Id @GeneratedValue private Long id; @NaturalId String companyCode; @NaturalId String employeeNumber; private String name; } // Lookup by composite natural ID: Employee emp = session.byNaturalId(Employee.class) .using("companyCode", "ACME") .using("employeeNumber", "EMP-001").load();
30. How does Hibernate 7 support vector data types for AI/ML applications?
Hibernate 7 (especially 7.2+) adds native support for vector data types used in AI embeddings, enabling semantic search alongside relational data.
import org.hibernate.annotations.Array; import org.hibernate.type.SqlTypes; @Entity public class Document { @Id @GeneratedValue private Long id; private String content; // Float32 vector (e.g. OpenAI text-embedding-3 embeddings): @JdbcTypeCode(SqlTypes.VECTOR) @Array(length=1536) // number of dimensions private float[] embedding; // Binary vector (Hibernate 7.2+): @JdbcTypeCode(SqlTypes.VECTOR_BINARY) @Array(length=24) private byte[] binaryVector; // Half-precision float16 (Hibernate 7.2+): @JdbcTypeCode(SqlTypes.VECTOR_FLOAT16) @Array(length=1536) private float[] halfPrecisionEmbedding; } // Vector similarity search (pgvector required): List<Object[]> similar = session .createNativeQuery( "SELECT d.id, d.content, d.embedding <=> :queryVec AS distance " + "FROM document d ORDER BY distance ASC LIMIT 10", Object[].class) .setParameter("queryVec", queryEmbedding) .getResultList(); // HQL distance functions (Hibernate 7.1+): List<Document> results = session .createQuery("FROM Document d ORDER BY cosine_distance(d.embedding, :vec) ASC", Document.class) .setParameter("vec", queryEmbedding).setMaxResults(10).getResultList();
31. What are the key removed APIs in Hibernate 7 and how do you migrate?
Hibernate 7 removes all APIs deprecated in Hibernate 6.x. Anything deprecated in 6.x is hard-deleted in 7.0 with no fallback.
| Removed | Replacement |
|---|---|
| session.save() | session.persist() |
| session.get() | session.find() |
| session.delete() | session.remove() |
| session.update() | session.merge() |
| session.saveOrUpdate() | session.merge() |
| session.lock(detached) | Load fresh with lock: session.find(Class, id, LockMode) |
| session.refresh(detached) | Only on managed entities |
| hibernate.allow_refresh_detached_entity | Removed entirely |
| Configuration.addClass() | Configuration.addAnnotatedClass() |
| hbm.xml mapping files | Annotation-based mapping or orm.xml |
| Ehcache 2.x integration | Ehcache 3.x, Caffeine, or Infinispan |
// Automated migration: // mvn rewrite:run -Drewrite.activeRecipes=org.openrewrite.hibernate.MigrateToHibernate70 // Manual: detached entity handling // BEFORE: session.update(detachedOrder); // REMOVED! detachedOrder.setStatus("DONE"); // AFTER: Order managed = session.merge(detachedOrder); managed.setStatus("DONE");
32. What is @SQLRestriction and @SQLJoinTableRestriction in Hibernate 7?
Hibernate 7 adds @SQLRestriction (replacing removed @Where) to apply a permanent SQL WHERE clause to an entity or collection mapping.
import org.hibernate.annotations.SQLRestriction; // Soft-delete: only ever load non-deleted records @Entity @SQLRestriction("deleted_at IS NULL") // applied to ALL queries public class Product { @Id private Long id; private String name; private LocalDateTime deletedAt; public void softDelete() { this.deletedAt = LocalDateTime.now(); } } // session.find(Product.class, id) -> only finds non-deleted products // 'FROM Product' HQL -> only returns non-deleted products // On a collection: @OneToMany(mappedBy="category") @SQLRestriction("deleted_at IS NULL") // only active products in collection private List<Product> activeProducts; // @SQLJoinTableRestriction: restrict on join table for @ManyToMany @ManyToMany @JoinTable(name="student_course") @SQLJoinTableRestriction("enrolled_at > CURRENT_DATE - INTERVAL '1 year'") private List<Course> recentCourses; // NOTE: @Where (old annotation) was REMOVED in Hibernate 7 // @Where(clause="deleted_at IS NULL") <- REMOVED // @SQLRestriction("deleted_at IS NULL") <- USE THIS
33. How does Hibernate 7 handle schema validation and generation (hbm2ddl)?
Hibernate can validate or generate the DB schema from entity mappings. Controlled by hibernate.hbm2ddl.auto.
| Value | Behaviour | Use in |
|---|---|---|
| none | No schema action | Production (use Flyway/Liquibase) |
| validate | Validates entity mappings match schema; fails on mismatch | Production safety check |
| update | Adds missing columns/tables; never drops | Development (risky in production) |
| create | Drops and recreates all tables on startup | Development/testing |
| create-drop | Creates on startup; drops on SessionFactory close | Unit tests |
# Recommended production setup: spring: jpa: hibernate: ddl-auto: none # Hibernate does NOT touch schema flyway: enabled: true # Flyway manages schema with versioned migrations locations: classpath:db/migration # Development: spring: jpa: hibernate: ddl-auto: create-drop # fresh schema each run # Flyway migration files: # db/migration/V1__create_products_table.sql # db/migration/V2__add_category_to_products.sql
34. What are Hibernate 7's improvements to multi-tenancy support?
Hibernate 7 enhances multi-tenancy with better integration with auto-enabled filters for discriminator-based tenancy.
| Strategy | Isolation | Use case |
|---|---|---|
| DATABASE | Highest - one DB per tenant | Regulated industries; highest cost |
| SCHEMA | High - one schema per tenant | Good balance; PostgreSQL/Oracle |
| DISCRIMINATOR | Lowest - tenant_id column | SaaS with many small tenants; lowest cost |
// DISCRIMINATOR (most common in Hibernate 7) @FilterDef( name="tenantFilter", parameters=@ParamDef(name="tenantId", type=String.class), autoEnabled=true // Hibernate 7: auto-enabled! ) @Filter(name="tenantFilter", condition="tenant_id = :tenantId") @Entity public class Order { @Id private Long id; private String tenantId; // discriminator column private BigDecimal total; } // FilterDefContributor SPI: auto-resolves tenantId from ThreadLocal public class TenantFilterContributor implements FilterDefContributor { @Override public void contribute(FilterDefTarget target) { target.registerFilterDefinition( FilterDefinition.of("tenantFilter", Map.of("tenantId", TenantContext::getCurrentTenantId)) ); } } // DATABASE strategy: supply different connections per tenant public class TenantConnectionProvider implements MultiTenantConnectionProvider<String> { @Override public Connection getConnection(String tenantId) { return dataSourceMap.get(tenantId).getConnection(); } }
35. What is the @Check constraint and @ColumnDefault annotation in Hibernate 7?
Hibernate 7 provides DDL-level annotations for database constraints and default values applied during schema generation.
@Entity @Check(name="price_positive", constraints="price > 0") // table-level check public class Product { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String name; @Column(nullable=false) @Check(name="stock_non_negative", constraints="stock >= 0") // column-level private int stock; @Column(precision=10, scale=2) private BigDecimal price; @ColumnDefault("'ACTIVE'") // DEFAULT 'ACTIVE' in DDL @Column(nullable=false) private String status; @ColumnDefault("CURRENT_TIMESTAMP") @Generated(EventType.INSERT) // re-read from DB after INSERT private LocalDateTime createdAt; } // Generated DDL: // CREATE TABLE product ( // id BIGINT NOT NULL, // stock INTEGER NOT NULL CHECK (stock >= 0), // price NUMERIC(10,2), // status VARCHAR(255) DEFAULT 'ACTIVE', // created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, // CONSTRAINT price_positive CHECK (price > 0) // )
36. What is @BatchSize and how does it solve the N+1 problem differently from JOIN FETCH?
@BatchSize loads lazy-loaded collections or proxies in batches using IN clauses rather than one-by-one. Unlike JOIN FETCH it works correctly with pagination.
@Entity public class Customer { @Id private Long id; private String name; @OneToMany(mappedBy="customer", fetch=FetchType.LAZY) @BatchSize(size=25) // load 25 collections at once private List<Order> orders; } // Without @BatchSize: // 1 query: SELECT * FROM customers LIMIT 100 // 100 queries: SELECT * FROM orders WHERE customer_id=1 ... customer_id=100 // Total: 101 queries // With @BatchSize(size=25): // 1 query: SELECT * FROM customers LIMIT 100 // 4 queries: SELECT * FROM orders WHERE customer_id IN (1..25) // SELECT * FROM orders WHERE customer_id IN (26..50) // SELECT * FROM orders WHERE customer_id IN (51..75) // SELECT * FROM orders WHERE customer_id IN (76..100) // Total: 5 queries! // Global default: # spring.jpa.properties.hibernate.default_batch_fetch_size=100 // @BatchSize on entity for proxy loading: @Entity @BatchSize(size=20) public class Product { ... }
37. What is Hibernate 7's @Struct mapping for database composite types?
Hibernate 7 adds @Struct for mapping Java records to database composite/structured types (native types in PostgreSQL, Oracle, DB2) stored as a single column value.
import org.hibernate.annotations.Struct; @Embeddable @Struct(name="address_type") // maps to the DB composite type public record AddressType( String street, String city, String postcode, String countryCode ) {} @Entity public class Customer { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String name; @Column(name="shipping_address") // single column of type address_type! private AddressType shippingAddress; } // PostgreSQL DDL: // CREATE TYPE address_type AS ( // street VARCHAR(255), // city VARCHAR(100), // postcode VARCHAR(10), // country_code VARCHAR(2) // ); // CREATE TABLE customer ( // id BIGINT PRIMARY KEY, // name VARCHAR(255), // shipping_address address_type -- single composite column! // ); // HQL querying the struct: List<Customer> london = session .createQuery("FROM Customer c WHERE c.shippingAddress.city = :city", Customer.class) .setParameter("city", "London").getResultList(); // Generated: WHERE (shipping_address).city = 'London'
38. What are Hibernate 7's logging improvements for SQL and parameter binding?
Hibernate 7 replaces the deprecated use_sql_comments property with a cleaner logging hierarchy. SQL parameter binding logging is now available natively without a JDBC proxy driver.
# Hibernate 7 logging configuration: logging: level: # SQL statement logging: org.hibernate.SQL: DEBUG # shows the SQL # SQL parameter values (NEW - no P6Spy proxy needed!): org.hibernate.orm.jdbc.bind: TRACE # logs actual parameter values # Output example: # binding parameter (1:VARCHAR) <- [alice@example.com] # binding parameter (2:BIGINT) <- [42] # Result set extraction: org.hibernate.orm.jdbc.extract: TRACE spring: jpa: show-sql: false # deprecated - use logging.level.org.hibernate.SQL properties: hibernate: format_sql: true generate_statistics: true # REMOVED in Hibernate 7: # use_sql_comments: true <- use logging.level instead // Hibernate statistics: Statistics stats = sf.getStatistics(); stats.setStatisticsEnabled(true); System.out.println("Queries: " + stats.getQueryExecutionCount()); System.out.println("L2 hits: " + stats.getSecondLevelCacheHitCount()); stats.logSummary();
39. How does Hibernate 7 work with Spring Boot 4 - what is auto-configured?
In Spring Boot 4, Hibernate 7 is the default JPA provider auto-configured transparently when spring-boot-starter-data-jpa is on the classpath.
<!-- spring-boot-starter-data-jpa includes: - hibernate-core 7.x - spring-data-jpa - HikariCP connection pool - jakarta.persistence-api 3.2 --> # Complete application.yml for Hibernate 7 in Spring Boot 4: spring: datasource: url: jdbc:postgresql://localhost:5432/mydb username: ${DB_USER} password: ${DB_PASSWORD} hikari: maximum-pool-size: 20 jpa: hibernate: ddl-auto: validate show-sql: false open-in-view: false # CRITICAL: disable OSIV in production! properties: hibernate: format_sql: true default_batch_fetch_size: 100 jdbc: batch_size: 50 order_inserts: true order_updates: true logging: level: org.hibernate.SQL: DEBUG org.hibernate.orm.jdbc.bind: TRACE
40. What are the key Hibernate 7 vs Hibernate 6 differences interviewers ask about?
The most common comparison question in enterprise Java interviews. Here is a structured summary of all major changes.
| Dimension | Hibernate 6 | Hibernate 7 |
|---|---|---|
| GA release | June 2022 | May 20, 2025 |
| Jakarta Persistence | 3.1 | 3.2 (complete) |
| Jakarta Data | Not supported | 1.0 (complete) |
| License | LGPL for Envers | All modules Apache Software License v2 |
| Detached reassociation | Deprecated (still worked) | COMPLETELY REMOVED (merge() only) |
| StatelessSession L2 cache | Bypassed | Uses L2 cache by default |
| hbm.xml support | Deprecated | REMOVED |
| Ehcache 2.x | Deprecated | REMOVED (use Ehcache 3/Caffeine) |
| @NaturalIdClass | Not available | NEW: composite natural IDs |
| @EmbeddedColumnNaming | Not available | NEW (incubating): prefix/suffix for embeddables |
| Vector types | Not available | NEW: VECTOR, VECTOR_BINARY, VECTOR_FLOAT16 |
| @SQLRestriction | Not available (used @Where) | NEW: replaces @Where |
| Auto-enabled filters | Not available | NEW: FilterDef autoEnabled |
| SelectionSpecification | Not available | NEW type-safe query builder |
| Key-based pagination | Preview (6.5) | Full support |
| FindOptions / Timeouts | Not available | NEW: Jakarta Persistence 3.2 API |
| TypedQueryReference | Not available | NEW: type-safe named query refs |
| @Struct | Preview | Full support |
| Spring Boot version | Spring Boot 3.x | Spring Boot 4.x |
