skip to content

Entity Mapping & Annotations

How a POJO becomes a row: identifiers and generation strategies, columns and converters, embeddables, enums and temporals, and inheritance hierarchies. Interviewers start here because mapping choices — IDENTITY vs SEQUENCE, SINGLE_TABLE vs JOINED — lock in performance behavior for the life of the schema.

part ofHibernateoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

What does the JPA specification require of a Java class before a provider such as Hibernate will accept it as a persistent entity, and what happens at runtime when one of those requirements is missing?

level: juniorimportance: must knowfreq 72%

answer

  1. @Entity + @Id + no-arg ctor + not final
  2. proxy = generated subclass → needs visible ctor, non-final
  3. hydrate after construct, not through constructor args
  4. final class = silent loss of lazy loading
  5. Serializable/@Table/setters not required

basics

~20 s

Mark the class @Entity, give it a no-arg constructor (public or protected), and declare an identifier with @Id. The class and its persistent members must not be final, otherwise Hibernate cannot instantiate it or build a lazy proxy.

solid answer

~50 s

A persistent class needs four things. It must be annotated `@Entity` (or declared in `orm.xml`). It must have a no-argument constructor that is at least protected, because the provider instantiates the object reflectively and then pushes column values into it. It must declare an identifier — `@Id` or `@EmbeddedId` — somewhere in its hierarchy. And it must not be `final`, nor have `final` persistent fields or getters, because Hibernate's lazy proxy is a runtime-generated subclass that overrides those members. It must also be a top-level class or a *static* nested class, not an inner class, and not an enum or interface. Failure modes differ. No `@Id` or no accessible constructor fails at bootstrap with a mapping exception. A `final` class boots fine but silently loses lazy loading: Hibernate cannot create a proxy factory, so associations to it are effectively fetched eagerly. `Serializable`, setters, and `@Table` are *not* required.

go deeper

for a junior

Recall the four essentials — @Entity, @Id, no-arg constructor, not final — and be able to write a minimal entity from memory.

for a middle

Explain why each rule exists: reflective instantiation plus hydration for the constructor, proxy subclassing for the non-final rule, persistence-context keying for the identifier.

for a senior

Add the failure modes and how you spot them — bootstrap exceptions versus the silent eager-fetch degradation from a final class or an inaccessible constructor — and mention records/Kotlin.

for a principal

Frame it as a modelling constraint: ORM-managed classes trade immutability and constructor-enforced invariants for change tracking and lazy loading, which is a reason to keep rich immutable value types out of the entity layer and behind embeddables or DTOs.

## What "entity" means An entity is a plain Java class whose instances the persistence provider maps to rows in a table and manages inside a persistence context. "Managed" means the provider tracks the object, can load it lazily, detect changes to it, and write those changes back as SQL. All of that is done by reflection and by runtime bytecode generation, and that machinery is exactly what the specification's requirements exist to protect. ## The requirements **1. The `@Entity` annotation (or an XML mapping entry).** Without it the class is invisible to the provider. Entities are discovered at bootstrap by scanning; a class the scanner does not see produces "Unknown entity" the first time you reference it in a query or in `persist()`. **2. A no-argument constructor.** The specification requires it to be `public` or `protected`. Hibernate is more forgiving for the entity class itself — it can call a package-private or even private constructor through reflection — but a lazy *proxy* is a generated subclass, and a subclass can only call a constructor that is visible to it. So a private no-arg constructor works until the first lazy association points at that entity. The constructor exists because the provider builds the instance *empty* and then hydrates it: it has no way to guess which argument of a rich constructor corresponds to which column. You may keep your convenient business constructors; just add the no-arg one alongside, typically `protected` so application code is discouraged from using it. **3. An identifier.** Every entity needs `@Id` (a single attribute) or `@EmbeddedId` / `@IdClass` (a composite). The identity is what the persistence context keys instances on and what the first-level cache uses to guarantee that one row equals one object within a session. A mapped class with no identifier fails at bootstrap. Note the contrast: `@Embeddable` types and `@MappedSuperclass` classes have no identifier of their own, which is precisely why they are not entities. **4. Nothing that blocks subclassing.** The class must not be `final`; persistent fields and getter methods must not be `final` either. Hibernate implements `getX()` returning an unloaded association by handing you an instance of a generated subclass that intercepts every method call and loads the row on first access. A `final` class cannot be subclassed, so no proxy can exist. Hibernate does not fail — it logs that it could not create a proxy factory and loads the association eagerly instead. This is the requirement that most often bites, because the symptom is a performance problem, not an error. **5. Class shape.** Top-level or static nested. A non-static inner class carries a hidden reference to its enclosing instance and has no usable no-arg constructor. Enums, interfaces and (in modern Java) records are excluded — a record is implicitly final and its fields are final, so it fails the proxy and hydration rules. The same is true of Kotlin classes, which are final by default and need a compiler plugin to be opened. ## What is *not* required - `Serializable` — only needed if you serialize detached instances or ship them across a remote boundary; the container never requires it for basic use. - Getters and setters — field access works fine, and many teams prefer it. - `@Table` — the table name defaults to the entity name run through the naming strategy. - Public visibility of persistent fields — they can and should be private. - A public constructor — protected is enough and is the better default. ## Diagnosing violations Bootstrap-time failures name the class and the missing piece: "No identifier specified for entity", or a reflection failure instantiating the class. Runtime surprises are the subtler ones — a `final` entity that never lazy-loads, or a private no-arg constructor that works in unit tests (where objects are constructed directly) and fails only when a real association is proxied. A quick review checklist for any new entity: annotation present, id present, protected no-arg constructor present, nothing final, class not an inner class.

  • Why can't a Java record or a Kotlin data class be used as an entity?
    Records are implicitly final with final fields and no no-arg constructor, so Hibernate can neither instantiate them empty nor generate a proxy subclass. Kotlin classes are final by default and hit the same proxy problem, which is why Kotlin projects use the all-open compiler plugin for entities. Both types are perfectly good as DTO or projection targets, just not as managed entities.
  • What is the practical consequence of marking an entity class final?
    Hibernate cannot create a proxy factory for it, so it logs a warning at bootstrap and treats to-one associations pointing at that class as eager. Every load of the owning entity pulls the associated row too, which quietly turns into extra queries or bigger joins. The mapping still works, so this shows up as a performance defect rather than an error.
  • Does an entity need to implement Serializable?
    No. It is required only when instances are serialized — for example, detached entities passed over a remote boundary or into a distributed session store. Composite identifier classes are a separate case: those must be Serializable and implement equals/hashCode.

Hibernate builds entities the way a factory builds a car body first and installs parts afterwards. It needs an empty shell it can open (no-arg constructor), a VIN to track it by (@Id), and a body it can weld an extra panel onto (non-final, for proxies).

saying these in an interview costs you the question

  • Claiming every entity must implement Serializable.
  • Claiming @Table is mandatory alongside @Entity.
  • Saying getters and setters are required, so field access is not allowed.
  • Saying a final entity class is fine because Hibernate uses reflection anyway.
  • Believing an entity can skip @Id if it is only ever read.

context

open as a page

What does the JPA @Column annotation control on a simple entity attribute, and what happens to an attribute that carries no @Column at all?

level: juniorimportance: must knowfreq 58%

basics

~20 s

@Column names the column and describes its shape — length, precision/scale, nullable, unique, columnDefinition — and controls whether it appears in INSERT/UPDATE via insertable/updatable. Omit it and the attribute still maps, using a default name and defaults such as length 255.

open as a page

In JPA/Hibernate, what does annotating a class @Embeddable and referencing it with @Embedded from another class actually do, and how does such a value type differ from a class annotated @Entity?

level: juniorimportance: must knowfreq 68%

basics

~20 s

@Embeddable marks a value type with no identity. Its fields become extra columns in the owner's table: no separate table, no primary key, no lifecycle of its own. An @Entity has an identifier, its own row, and is tracked individually by the persistence context.

open as a page

How does JPA's @Enumerated annotation store a Java enum in the database, what is its default, and what is the practical difference between EnumType.ORDINAL and EnumType.STRING?

level: juniorimportance: must knowfreq 72%

basics

~20 s

By default (ORDINAL) JPA stores the enum constant's declaration index as a number, so reordering or inserting constants silently remaps existing rows. EnumType.STRING stores the constant's name, which is stable under reordering and readable, but breaks if you rename a constant.

open as a page

Do JPA/Hibernate entity classes need custom equals() and hashCode(), or is the reference comparison inherited from java.lang.Object good enough? When does the choice actually start to matter?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Object.equals compares references. That is correct only while every instance comes from one open persistence context, which guarantees one object per row. As soon as entities are detached, re-loaded in another session, merged, or compared across sessions, two objects represent the same row and reference equality says false — so you override equals/hashCode using a stable identifier.

open as a page

What identifier generation strategies can JPA's @GeneratedValue use, how do they differ in the SQL they produce, and which one does strategy = AUTO actually pick under Hibernate?

level: juniorimportance: must knowfreq 75%

basics

~20 s

IDENTITY uses an auto-increment column, so the id is known only after the INSERT. SEQUENCE calls a database sequence, so the id is known before. TABLE keeps counters in a table row. UUID generates one in the JVM. Hibernate's AUTO picks SEQUENCE, or UUID for UUID-typed ids.

open as a page

In JPA, how does the provider decide whether to read and write an entity's state through its fields or through its getters and setters, and why does the placement of the @Id annotation matter for that decision?

level: middleimportance: must knowfreq 58%

basics

~20 s

Access type is decided by where the mapping annotations — specifically @Id or @EmbeddedId — are placed. On fields it is FIELD access; on getters it is PROPERTY access, and the provider then calls getters and setters instead of touching fields. @Access overrides the default.

open as a page

Explain how a JPA AttributeConverter works, how you attach one to an attribute with @Convert versus applying it automatically, and which kinds of attributes you are not allowed to convert.

level: middleimportance: must knowfreq 52%

basics

~20 s

AttributeConverter<X,Y> defines convertToDatabaseColumn and convertToEntityAttribute, translating between a Java type and a column type. Attach it per attribute with @Convert, or globally for the type with @Converter(autoApply = true). Identifiers, version attributes and associations cannot be converted.

open as a page

JPA offers two ways to map a composite primary key: @EmbeddedId and @IdClass. How do they differ in mapping, in query syntax, and in when you would choose one over the other?

level: middleimportance: must knowfreq 58%

basics

~20 s

@EmbeddedId puts the key in one @Embeddable field, so you access and query it through that field (entity.id.orderId). @IdClass declares the key properties directly on the entity with @Id each, plus a mirror class listing them; queries use flat paths. Both key classes need Serializable, a no-arg constructor and equals/hashCode.

open as a page

When mapping date and time attributes with JPA/Hibernate, how do java.time types Instant, LocalDate, LocalDateTime, OffsetDateTime and ZonedDateTime differ in what they store, and how do you choose between them?

level: middleimportance: must knowfreq 56%

basics

~20 s

LocalDate maps to DATE, LocalTime to TIME, LocalDateTime to a timestamp without zone (wall-clock, no instant), Instant to a point on the timeline, and OffsetDateTime/ZonedDateTime carry an offset that survives only with a with-time-zone column or explicit offset storage. Use Instant for events, LocalDate/LocalDateTime for calendar facts.

open as a page

You override equals() and hashCode() on a JPA entity using its database-generated primary key, add a brand-new instance to a HashSet, and then persist it. Why can the set stop finding that object, and how do you write equals/hashCode so it cannot happen?

level: middleimportance: must knowfreq 66%

basics

~20 s

Before persist the generated id is null, so hashCode is computed from null; at flush the id is assigned and hashCode changes, but the object still sits in the bucket chosen by the old hash — contains() and remove() miss it. Fix: constant hashCode plus id-equality, or an identifier assigned before persist.

open as a page

What does the allocationSize attribute of JPA's @SequenceGenerator control in Hibernate, and what goes wrong if it disagrees with the INCREMENT BY of the underlying database sequence?

level: middleimportance: must knowfreq 45%

basics

~20 s

allocationSize is how many ids Hibernate reserves per sequence call and hands out from memory — default 50. If the database sequence increments by 1 while Hibernate assumes 50, the reserved block overlaps values a later call returns, producing duplicate primary keys.

open as a page

Why does using an auto-increment (IDENTITY) column for entity identifiers stop Hibernate from batching INSERT statements, and what changes if you switch to a database sequence?

level: middleimportance: must knowfreq 55%

basics

~20 s

With IDENTITY the id only exists once the row is inserted, but Hibernate needs an id to key the entity in the persistence context. So it executes the INSERT immediately at persist(), one statement per entity — nothing is queued, so nothing can batch. A sequence gives the id up front, letting inserts queue and batch at flush.

open as a page

JPA's @Inheritance annotation offers SINGLE_TABLE, JOINED and TABLE_PER_CLASS. Describe the table layout each one produces and the main trade-off of each.

level: middleimportance: must knowfreq 68%

basics

~20 s

SINGLE_TABLE puts the whole hierarchy in one table with a discriminator column; fastest, but subclass columns must be nullable. JOINED gives each class its own table joined by primary key; normalized with NOT NULL constraints, costs joins. TABLE_PER_CLASS gives each concrete class a full standalone table; polymorphic queries become UNIONs.

open as a page

In Hibernate, what is the difference between an ImplicitNamingStrategy and a PhysicalNamingStrategy, and when does each one run while the mapping model is built?

level: middleimportance: must knowfreq 45%

basics

~20 s

ImplicitNamingStrategy invents a logical name when the mapping does not state one (entity name, field name, join-table name). PhysicalNamingStrategy then turns every logical name - invented or explicitly annotated - into the actual database identifier Hibernate emits in SQL.

open as a page

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%

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.

open as a page

In a JPA entity hierarchy mapped to one shared table, what is the role of the @DiscriminatorColumn and @DiscriminatorValue annotations, and what are the defaults if you declare neither?

level: juniorimportance: should knowfreq 45%

basics

~20 s

They tell Hibernate which concrete subclass a row belongs to. @DiscriminatorColumn names that column on the root entity; @DiscriminatorValue sets the value stored per subclass. Defaults: a String column named DTYPE holding the entity name. Hibernate writes it on insert and filters on it when querying a subclass.

open as a page

A Hibernate entity field named createdAt is mapped to a column literally called createdAt, but the database convention is created_at. How do you make Hibernate map camelCase Java names to snake_case identifiers across the whole schema?

level: juniorimportance: should knowfreq 40%

basics

~10 s

Set hibernate.physical_naming_strategy to a snake-casing implementation - Hibernate ships CamelCaseToUnderscoresNamingStrategy. It rewrites every table, column and sequence name, including ones written explicitly in annotations, so you do not annotate each field.

open as a page

What is the difference between marking a field with the JPA @Transient annotation and with the Java `transient` keyword, and what does the JPA @Basic annotation actually control?

level: middleimportance: should knowfreq 48%

basics

~20 s

Both exclude a field from persistence, but the Java keyword also excludes it from Java serialization while @Transient does not. @Basic is optional metadata for simple attributes: optional=false is a nullability hint and fetch=LAZY is only a hint too.

open as a page

When would you map a column with the JPA @Column attributes insertable = false and updatable = false, and what does the provider do differently once you set them?

level: middleimportance: should knowfreq 45%

basics

~20 s

They remove the column from generated INSERT and UPDATE statements while it is still read on SELECT. Use them for database-maintained columns and for a foreign key mapped twice — once as an association, once as a plain field. The in-memory value can go stale.

open as a page

An entity needs two attributes of the same @Embeddable type — for example a billing address and a shipping address — and both would map to identical column names. How do you make that work in JPA/Hibernate, including when the embeddable itself contains a nested embeddable or an association?

level: middleimportance: should knowfreq 52%

basics

~10 s

Use @AttributeOverride on each embedded attribute to rename the columns, one per property. Nested embeddable properties are addressed with dotted paths like "geo.latitude". Associations inside the embeddable are renamed with @AssociationOverride instead.

open as a page

What is the JPA @Temporal annotation for, when is it required, and why should it not appear on attributes typed with the java.time API?

level: middleimportance: should knowfreq 36%

basics

~20 s

@Temporal exists only for the legacy types java.util.Date and java.util.Calendar, which are ambiguous: it tells the provider whether to map DATE, TIME or TIMESTAMP. java.time types already carry that meaning, so @Temporal is unnecessary and not permitted on them.

open as a page

A column is populated by the database itself — a DEFAULT, a trigger, or a computed column. How do you make Hibernate load that value back into the in-memory entity after an INSERT or UPDATE, and what does it cost in SQL?

level: middleimportance: should knowfreq 40%

basics

~20 s

Mark the property with Hibernate's @Generated, naming the events (INSERT, UPDATE) at which the database produces it. Hibernate then omits the column from the write statement and reads the value back — with an extra SELECT, or via INSERT ... RETURNING on dialects that support it.

open as a page

Compare Hibernate's @CreationTimestamp and @UpdateTimestamp with letting the database fill created_at/updated_at through a DEFAULT or a trigger. Where is the value produced in each case, and what breaks?

level: middleimportance: should knowfreq 45%

basics

~20 s

@CreationTimestamp and @UpdateTimestamp are produced in the JVM by Hibernate when it inserts or updates the entity, so they depend on the app server's clock and are skipped by bulk JPQL updates and native SQL. Database defaults and triggers fire for every writer but need a read-back to appear in the entity.

open as a page

What does Hibernate's @NaturalId annotation give you beyond declaring a plain unique column, and how does loading through Session.byNaturalId() / bySimpleNaturalId() differ from writing a query on that column?

level: middleimportance: should knowfreq 30%

basics

~20 s

@NaturalId marks the domain's real business key alongside the surrogate @Id. Hibernate then offers byNaturalId()/bySimpleNaturalId() lookups that resolve the natural key to the primary key and load through the normal identity map and caches, so a repeat lookup can avoid SQL entirely. It also enforces immutability by default.

open as a page

In JPA, what is the difference between putting @MappedSuperclass on a shared base class and making that base class an @Entity with @Inheritance, and how do you decide which one a shared base belongs in?

level: middleimportance: should knowfreq 52%

basics

~20 s

@MappedSuperclass only shares column mappings: it has no table, cannot be queried, and nothing can reference it as an association — each subclass table simply gets copies of its columns. Entity inheritance makes the base a real queryable type with polymorphic queries and associations. Choose by whether you need polymorphism.

open as a page

An entity declares @Table(name = "USER_ACCOUNT") and @Column(name = "emailAddress"). Does a configured Hibernate PhysicalNamingStrategy still get a say in those names? What about the ImplicitNamingStrategy?

level: middleimportance: should knowfreq 25%

basics

~20 s

The implicit strategy is skipped - a name was given. The physical strategy still runs: the annotated string is the logical name, and every logical name is converted by the physical pass, so it can be rewritten.

open as a page

The JPA @Table annotation lets you set name, schema, catalog and uniqueConstraints. Which of those affect the SQL a provider emits at runtime, and which only matter when it generates DDL?

level: seniorimportance: should knowfreq 38%

basics

~20 s

name, schema and catalog become the qualified table name in every statement, so they are runtime. uniqueConstraints and indexes are consumed only by schema generation — the provider never enforces them in memory, so violations surface as database errors at flush.

open as a page

An entity maps a boolean to the characters 'Y' and 'N' through a JPA AttributeConverter. What can go wrong when you query, sort or index that column, and how do you work around it?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Conversion happens at parameter binding and result extraction only. Bound query parameters are converted; native SQL is not, and any database-side expression — sorting, LIKE, functions, index order — sees the stored form ('Y'/'N'), not the Java value.

open as a page

How do you map a JSON document column — for example a PostgreSQL jsonb column — onto an entity attribute with Hibernate, and what must you watch out for once it is mapped?

level: seniorimportance: should knowfreq 38%

basics

~20 s

In Hibernate 6, annotate the attribute @JdbcTypeCode(SqlTypes.JSON) and give the column a json/jsonb definition; Hibernate serializes the object for you. Watch dirty checking of mutable payloads, whole-document rewrites, and that JPQL cannot navigate inside the document.

open as a page

showing 1–30 of 46