@Entity

Marks a class as a JPA entity: Hibernate maps it to a table, tracks its changes and lets you query it by its entity name in JPQL.

Signature
@Entity
@Entity(name = "Book")
  • Added in Spring Boot JPA 1.0 (javax.persistence); jakarta.persistence since Jakarta Persistence 3.0

Parameters

ParameterTypeDescription
$nameoptionalString= the unqualified class nameThe entity name used in JPQL queries. It does not change the table name; use @Table for that.

Return value

n/aRegisters the class with the persistence unit so Hibernate maps it to a table, manages its instances and allows it in JPQL queries.

Errors

A class without an @Id field, or one outside the scanned packages, fails at startup (for example 'Not a managed type' when a repository refers to it).

@Entity tells Hibernate that a class is persistent. Each instance is one row, each persistent field is one column, and every entity needs exactly one identifier marked with @Id. Spring Boot finds entities by scanning the package of your @SpringBootApplication class and everything below it, the same packages it scans for components.

What the class must look like

  • A no-argument constructor that is public or protected. Hibernate calls it when it loads a row. Make it protected so application code uses your real constructor.
  • Not final, and no final persistent methods. Hibernate creates subclass proxies for lazy references; a final class cannot be proxied, so lazy loading quietly turns into eager loading.
  • Mutable fields. A Java record cannot be an entity, because records are final and their fields cannot be set after construction. Records are excellent for DTOs and projections instead.

Entity name and table name

The name attribute sets the entity name, which is what JPQL uses: select b from Book b. It is not the table name. Use @Table(name = "books") to change the table. By default Spring Boot maps camelCase names to snake_case, so a class OrderLine becomes table order_line. Words such as order and user are reserved in SQL, so an entity called Order or User needs an explicit table name or the generated SQL fails.

Common mistakes

Entities outside the main package are not found, and the first repository that refers to one fails with Not a managed type. Move the class, or add @EntityScan (in org.springframework.boot.persistence.autoconfigure in Boot 4) listing the extra packages.

Returning entities straight from controllers couples your JSON to your schema, can trigger lazy loading during serialisation and exposes fields you never meant to publish. Map them to record DTOs at the service boundary.

Writing equals and hashCode over every field breaks when the id is generated: the hash changes after persist and the object is lost inside a HashSet. Either leave the defaults, or compare by id and return a constant hash code.

@Entity
@Table(name = "orders")
class Order {
    @Id @GeneratedValue
    private Long id;
    protected Order() { }
}

In production, let a migration tool such as Flyway create the tables and set spring.jpa.hibernate.ddl-auto=validate, so a mismatch between the entity and the schema stops the application at startup rather than failing on the first request.

Example

Example · java
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import org.springframework.data.jpa.repository.JpaRepository;

@Entity
@Table(name = "products")
public class Product {

    @Id
    @GeneratedValue
    private Long id;

    @Column(nullable = false, length = 120)
    private String name;

    @Column(nullable = false, precision = 10, scale = 2)
    private BigDecimal price;

    protected Product() { } // for Hibernate

    public Product(String name, BigDecimal price) {
        this.name = name;
        this.price = price;
    }

    public Long getId() { return id; }
    public String getName() { return name; }
    public BigDecimal getPrice() { return price; }

    public void reprice(BigDecimal newPrice) {
        if (newPrice.signum() < 0) {
            throw new IllegalArgumentException("price must not be negative");
        }
        this.price = newPrice;
    }
}

interface ProductRepository extends JpaRepository<Product, Long> { }

When to use it

  • Mapping an orders table to an Order entity whose table name avoids the reserved SQL word order.
  • Pulling entities from a shared billing module into an application whose main class lives in a different package.
  • Giving a legacy table a cleaner entity name for JPQL while keeping the existing table untouched.

Frequently asked questions

Can a Java record be a JPA entity?

No. Records are final and immutable, while Hibernate needs a no-argument constructor, a proxyable class and fields it can set when loading a row. Use a class for the entity and records for DTOs and query projections.

Why do I get 'Not a managed type' at startup?

Hibernate never saw the class as an entity. Either the @Entity annotation is missing, it is the wrong import, or the class sits outside the packages Spring Boot scans. Move it below the main application class or add @EntityScan with the right packages.

What is the difference between @Entity(name) and @Table(name)?

@Entity(name) is the name JPQL queries use. @Table(name) is the database table. The entity name defaults to the class name, and the table name defaults to the entity name passed through the naming strategy (snake_case). So @Entity(name = "Customer") alone also maps to table customer; add @Table to keep the table name fixed.

More examples

Entity name versus table name

The JPQL query refers to Customer because that is the entity name; Hibernate translates it to SQL against customer_accounts. Renaming the class later does not break the query while the entity name stays fixed.

Example · java
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

@Entity(name = "Customer")
@Table(name = "customer_accounts")
class CustomerAccount {
    @Id
    @GeneratedValue
    private Long id;
    private String email;
    private boolean active;

    protected CustomerAccount() { }
}

public interface CustomerAccountRepository extends JpaRepository<CustomerAccount, Long> {

    // JPQL uses the entity name "Customer", SQL hits table customer_accounts
    @Query("select c from Customer c where c.active = true")
    List<CustomerAccount> findActive();
}

Equality that survives a generated id

Two unsaved tags are never equal, and a saved tag equals any other instance with the same id, including a Hibernate proxy: instanceof accepts the proxy subclass, and the other side is read through getId() because a proxy's own fields are empty. The constant hash code keeps the object findable in a HashSet after the id is assigned.

Example · java
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import java.util.Objects;

@Entity
public class Tag {
    @Id
    @GeneratedValue
    private Long id;
    private String label;

    protected Tag() { }

    public Tag(String label) { this.label = label; }

    public Long getId() { return id; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Tag other)) return false;
        return id != null && Objects.equals(id, other.getId()); // getter, not field: works on proxies
    }

    @Override
    public int hashCode() {
        return Tag.class.hashCode(); // constant, so it never changes after persist
    }
}

Scanning entities in another package

Only needed when entities live outside the main class's package, for example in a shared module. @EntityScan replaces the default scan rather than adding to it, so list your own package too; the same applies to repositories with @EnableJpaRepositories.

Example · java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.persistence.autoconfigure.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@EntityScan(basePackages = {"com.example.shop", "com.example.shared.billing"})
@EnableJpaRepositories(basePackages = {"com.example.shop", "com.example.shared.billing"})
public class ShopApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShopApplication.class, args);
    }
}

Discussion

  • Be the first to comment on this lesson.