Skip to content
Spring Boot sb data-jpa 4 min read

@Entity & Column Mapping

An entity is a plain Java class that Hibernate maps to a database table. The jakarta.persistence annotations let you control exactly how fields become columns: their names, types, nullability, and length. This page covers the everyday mapping annotations you reach for on almost every entity. For the broader picture, see the Spring Data JPA intro.

A minimal entity

Every entity needs the @Entity annotation, an @Id, and a no-args constructor (JPA instantiates entities reflectively).

import jakarta.persistence.*;

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private BigDecimal price;

    protected Product() { } // required by JPA

    public Product(String name, BigDecimal price) {
        this.name = name;
        this.price = price;
    }
    // getters and setters
}

Without a @Table, Hibernate derives the table name from the class name using the active naming strategy (covered below). For Product that becomes the product table.

@Table — controlling the table

Use @Table to set the table name explicitly, place it in a schema, or declare table-level constraints and indexes.

@Entity
@Table(
    name = "products",
    schema = "catalog",
    uniqueConstraints = @UniqueConstraint(columnNames = {"sku"}),
    indexes = @Index(name = "idx_product_name", columnList = "name")
)
public class Product { /* ... */ }

Tip: Pluralizing table names (products) while keeping singular class names (Product) is a common convention. @Table(name = "products") makes that mapping explicit.

@Column — per-field control

@Column is optional, but it’s where you tune the most important physical details.

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

@Column(unique = true, updatable = false)
private String sku;

@Column(precision = 10, scale = 2)
private BigDecimal price;
AttributeEffectDDL impact
nameColumn name (defaults to field name)product_name
nullablefalse adds NOT NULLnot null
lengthMax length for VARCHAR (default 255)varchar(120)
uniqueAdds a unique constraintunique
precision / scaleTotal/fractional digits for decimalsnumeric(10,2)
insertable / updatableExclude from generated INSERT/UPDATE

With ddl-auto: create, the entity above generates:

create table products (
    id bigint not null auto_increment,
    product_name varchar(120) not null,
    sku varchar(255),
    price numeric(10,2),
    primary key (id)
) engine=InnoDB

Warning: @Column(nullable = false) only affects schema generation. It is not a runtime validation. Use Bean Validation (@NotNull) to reject bad input before it reaches the database.

@Enumerated — persisting enums

By default JPA stores enums by their ordinal() (an int). That is fragile: reordering enum constants silently corrupts existing data. Always use EnumType.STRING.

public enum Status { ACTIVE, ARCHIVED, OUT_OF_STOCK }

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private Status status;
status varchar(20) not null   -- stores 'ACTIVE', not 0

Warning: EnumType.ORDINAL (the default) breaks the moment someone inserts a new constant in the middle of the enum. Set EnumType.STRING on every enum field.

@Lob — large objects

@Lob maps very large content to CLOB (text) or BLOB (binary) columns.

@Lob
private String description;   // CLOB / TEXT

@Lob
private byte[] image;         // BLOB

@Transient — excluding a field

Annotate a field with @Transient to keep it in your Java object but out of the database entirely. It is ideal for derived or computed values.

@Transient
private BigDecimal priceWithTax;   // computed at runtime, never persisted

@Temporal and modern date/time types

With Java 8+ date/time types (LocalDate, LocalDateTime, Instant), Hibernate 6 maps them correctly out of the box — @Temporal is not needed.

private LocalDate releaseDate;        // maps to DATE
private LocalDateTime createdAt;      // maps to TIMESTAMP
private Instant lastModified;         // maps to TIMESTAMP

@Temporal is only relevant for the legacy java.util.Date/Calendar types, where you must declare the precision:

@Temporal(TemporalType.TIMESTAMP)
private java.util.Date legacyTimestamp;

Tip: Prefer java.time types. They are immutable, unambiguous, and need no @Temporal. See Java Date/Time usage patterns for working with them.

Naming strategies

When you don’t supply explicit name attributes, Hibernate derives column and table names through two strategies. Spring Boot’s default implicit strategy turns camelCase into snake_case.

Java fieldDefault column
productNameproduct_name
releaseDaterelease_date
SKUsku

The physical naming strategy is configured in application.yml:

spring:
  jpa:
    hibernate:
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
        implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
    properties:
      hibernate:
        format_sql: true

Note: Spring Boot’s default CamelCaseToUnderscoresNamingStrategy is what converts releaseDate to release_date. Override it only if you need to match a legacy schema, and prefer explicit @Column(name = ...) for one-off exceptions.

Putting it together

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    @Column(unique = true, updatable = false)
    private String sku;

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

    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 20)
    private Status status = Status.ACTIVE;

    @Lob
    private String description;

    private LocalDateTime createdAt = LocalDateTime.now();

    @Transient
    private BigDecimal priceWithTax;

    protected Product() { }
    // constructor, getters, setters
}
Last updated June 13, 2026
Was this helpful?