skip to content

@Entity Basics & Access Types

The ground rules a class must satisfy to become an entity and how Hibernate decides whether to read your fields or your getters. Interviewers probe these basics to check you understand what the annotations demand rather than copying boilerplate.

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

questions

5

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

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

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

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

How do you make one attribute of a JPA entity use property access while the rest of the class uses field access, and when is that worth doing?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Declare the class default explicitly with @Access(AccessType.FIELD), mark the backing field @Transient so it is not mapped twice, then put @Access(AccessType.PROPERTY) plus the mapping annotations on the getter. Hibernate then calls that getter and setter for that one column.

open as a page