skip to content

In Hibernate, what does the @Formula annotation on an entity property do, where does its value come from, and what are its limitations?

level: juniorimportance: should knowfreq 40%

answer

  1. Read-only SQL in the select list
  2. Native SQL, not JPQL
  3. Stale until refresh()
  4. Correlated subquery = per-row cost
  5. No column to index

basics

~20 s

@Formula maps a property to a raw SQL expression that Hibernate splices into the entity's SELECT. It is read-only: never written on INSERT or UPDATE, recomputed by the database on each load, and stale in memory until the entity is refreshed.

solid answer

~50 s

`@Formula("<sql>")` says the property has no column of its own; the SQL fragment goes into the select list of every query that loads the entity and is read back into the field. It is native SQL, not JPQL, evaluated by the database against that row, and it may reference other columns of the same table or use a correlated subquery. It is read-only: it never appears in INSERT/UPDATE and dirty checking ignores it, so assigning it in Java changes nothing. It goes stale — change the columns it depends on and the field keeps the value read at load time until reload or `EntityManager.refresh()`. It is always paid: computed for every row of every entity load, including lazy proxy initialisation. And it is vendor SQL, so it must be rewritten if you change database. Good for small derived values you only read.

code

java · 14 lines
java
@Entity
public class Order {
    @Id
    private Long id;

    private BigDecimal unitPrice;
    private int quantity;

    @Formula("unit_price * quantity")
    private BigDecimal total;

    @Formula("(select count(*) from order_line l where l.order_id = id)")
    private int lineCount;
}

go deeper

for a junior

Say what it is: a read-only SQL snippet added to the SELECT, computed by the database, not written back.

for a middle

Add the mechanics: excluded from INSERT/UPDATE and from dirty checking, stale until refresh, usable in JPQL where it is textually substituted.

for a senior

Talk about cost — per-row evaluation on every load and every lazy initialisation, no index, and when to move the value to a stored generated column or a view instead.

for a principal

Frame it as where derived state should live: entity-local convenience versus a database-enforced computation that every writer and every consumer shares, and the portability debt of vendor SQL inside mappings.

## What it is `org.hibernate.annotations.Formula` is Hibernate-specific, not part of JPA. On a field or getter it declares: *this property has no column; compute it with this SQL expression*. Hibernate injects the expression into the select list of the entity's SELECT, aliases it, and populates the field from the result set. The fragment is **native SQL**, not JPQL. Unqualified column names are resolved against the entity's own table, so `@Formula("unit_price * quantity")` works on the row being loaded. Because it is real SQL you can also write a correlated subquery — `(select count(*) from order_line l where l.order_id = id)` — or call vendor functions. ## What Hibernate does with it On `em.find(Order.class, 1L)` Hibernate emits roughly: ``` select o.id, o.status, (unit_price * quantity) as formula0 from orders o where o.id = ? ``` The property participates in the loaded state like any other, so it is readable in Java and usable in HQL/JPQL and Criteria as a path — Hibernate substitutes the expression wherever the property is referenced, which means `where o.total > 100` becomes `where (unit_price * quantity) > 100`. ## Read-only, and what that implies The property is excluded from the INSERT and UPDATE statements Hibernate generates, and it is not part of dirty checking. Setting it in Java has no persistent effect at all, so the setter (if any) is a lie — expose it as a getter over a private field and never write to it. Because the value is produced by the database at *load* time, it is a snapshot. If you load an order, change a line quantity, and flush, the formula field in memory is not recalculated: Hibernate would have to re-SELECT the row to know the new value. `EntityManager.refresh(order)` does exactly that; so does evicting and reloading. This staleness surprises people who expect it to behave like a Java getter. ## Costs The expression runs for every row returned by every query that loads the entity, and for every lazy proxy that gets initialised. A scalar arithmetic expression is essentially free. A correlated subquery is not: fetching 500 entities runs the subquery 500 times inside one statement, and the database usually cannot use an index for it the way it could for a plain column. Filtering or sorting on a formula generally means recomputing it for every candidate row, because there is no column to index (unless the database supports an expression index whose expression matches exactly). A second cost is portability and tooling: the fragment bypasses the dialect, so vendor functions, quoting and casting rules leak into the mapping. Column-name resolution also collides with naming strategies — the formula must use the *physical* column names, not the Java property names. ## When to use it, and the alternatives Use `@Formula` for small, cheap, read-only derivations that you want visible on the entity without a schema change: a concatenated display name, a simple arithmetic total, a boolean flag like `(end_date < current_date)`. Reach for something else when the value is expensive or heavily queried: - A **stored generated column** (`GENERATED ALWAYS AS (...) STORED`) computes at write time, can be indexed, and applies to every writer; map it read-only with Hibernate's `@Generated`. - A **database view** mapped as an immutable entity is better for reporting shapes that join several tables. - A **plain Java method** on the entity is better when nothing needs to filter or sort on the value in SQL. A related annotation is `@JoinFormula`, which does the same trick for an association's join condition. Both are power tools: they make the mapping do things JPA cannot express, at the price of vendor SQL inside your entity.

  • Can you filter or sort on a @Formula property in a JPQL query?
    Yes — Hibernate substitutes the SQL fragment wherever the property path appears, so `order by o.total` becomes `order by (unit_price * quantity)`. It works, but the database must evaluate the expression for every candidate row and normally cannot use an index, so it is fine on small result sets and dangerous on large tables.
  • You updated the quantity and flushed, but the formula field still shows the old total. Why?
    The formula value is populated only when the row is read. Flushing sends an UPDATE; it does not re-SELECT the row, so the in-memory snapshot is untouched. Call `EntityManager.refresh(entity)` after the flush, or reload the entity in a later query, to see the recomputed value.

Like a spreadsheet cell with a formula that only recalculates when you reopen the file: the value looks live, but in memory it is frozen at load time.

saying these in an interview costs you the question

  • Thinking the formula is recalculated in memory whenever dependent fields change
  • Writing to the property and expecting it to persist
  • Assuming the expression is JPQL, so using entity/property names instead of table/column names
  • Putting an expensive correlated subquery in a formula on an entity that is loaded in bulk
  • Believing @Formula is standard JPA and therefore portable

context