@Column

Customises how an entity field maps to its column: name, nullability, length, precision, uniqueness, and whether Hibernate includes it in INSERT and UPDATE.

Signature
@Column(name = "", nullable = true, length = 255, precision = 0, scale = 0,
        unique = false, insertable = true, updatable = true, columnDefinition = "")
  • Added in Spring Boot JPA 1.0 (javax.persistence)

Parameters

ParameterTypeDescription
$nameoptionalString= field nameColumn name. Defaults to the field name, converted to snake_case by Spring Boot's naming strategy.
$nullableoptionalboolean= trueWhether the generated DDL allows NULL.
$lengthoptionalint= 255Column length for string columns in generated DDL.
$precisionoptionalint= 0Total digits for decimal columns (BigDecimal).
$scaleoptionalint= 0Digits after the decimal point for decimal columns.
$uniqueoptionalboolean= falseAdds a single-column unique constraint in generated DDL.
$insertableoptionalboolean= trueWhether Hibernate includes the column in INSERT statements.
$updatableoptionalboolean= trueWhether Hibernate includes the column in UPDATE statements.
$columnDefinitionoptionalString= ""Raw SQL type fragment used in generated DDL. Ties the mapping to one database.
$tableoptionalString= primary tableThe secondary table that holds the column.
$commentoptionalString= ""Column comment for generated DDL.
$checkoptionalCheckConstraint[]= {}Check constraints for generated DDL.
$optionsoptionalString= ""Extra SQL fragment appended to the column definition in generated DDL.
$secondPrecisionoptionalint= provider defaultFractional-second precision for time columns.

Return value

n/aOverrides the default mapping of one persistent field or property to a table column.

Errors

With ddl-auto=validate, a column that is missing or has an incompatible type fails startup with a schema validation error.

Changelog

  • PHP Jakarta Persistence 3.2Added comment, check, options and secondPrecision.

Every non-static, non-transient field of an entity is persistent whether or not it has @Column. You add the annotation only to change something: the column name, the generated DDL, or whether Hibernate writes the column at all.

Schema hints versus runtime behaviour

The attributes fall into two groups, and confusing them causes most surprises.

  • DDL only: nullable, length, unique, precision, scale, columnDefinition. They shape the CREATE TABLE Hibernate generates. When Flyway owns the schema, they are documentation; the database constraints in your migration are what actually reject bad data.
  • Runtime: name, insertable and updatable change the SQL Hibernate runs on every save.

So @Column(length = 50) does not stop a 60-character name reaching the database; the insert fails there with a data error, which surfaces as a 500. Validate input at the edge with Bean Validation (@Size(max = 50)) and keep the column definition in the migration consistent with it.

Money and precision

Map money to BigDecimal and always set precision and scale in generated schemas; never use double, which cannot represent 0.10 exactly. A scale of 2 suits most currencies, though some have 0 or 3 decimal places.

Read-only columns

updatable = false keeps a value fixed after the first insert. It is the right setting for a creation timestamp or an external reference that must never change. Hibernate simply leaves the column out of the UPDATE: changing the field in Java is silently ignored, not reported. insertable = false, updatable = false together make a column read-only, useful for values the database computes.

@Column(name = "created_at", nullable = false, updatable = false)
private Instant createdAt;

Mistakes to avoid

columnDefinition = "jsonb" or "text" ties the generated DDL to one database, and it only changes DDL: Hibernate still binds the value with the Java type's normal JDBC type, so a String sent to a jsonb column fails at run time. Map JSON with @JdbcTypeCode(SqlTypes.JSON), let the migration create the column and keep the annotation portable. And remember the default length of 255: a description field mapped without thought becomes varchar(255) in generated schemas, and a long product description is rejected in production.

Example

Example · java
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import java.math.BigDecimal;
import java.time.Instant;

@Entity
public class Invoice {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "invoice_number", nullable = false, unique = true, length = 20, updatable = false)
    private String number;

    @Column(nullable = false, precision = 12, scale = 2)
    private BigDecimal total;

    @Column(nullable = false, length = 3)
    private String currency;

    @Column(name = "issued_at", nullable = false, updatable = false)
    private Instant issuedAt;

    @Column(length = 2000)
    private String notes;

    protected Invoice() { }

    public Invoice(String number, BigDecimal total, String currency) {
        this.number = number;
        this.total = total;
        this.currency = currency;
        this.issuedAt = Instant.now();
    }

    public String getNumber() { return number; }
    public BigDecimal getTotal() { return total; }
}

When to use it

  • Mapping a price to numeric(10, 2) so totals never suffer floating-point rounding.
  • Freezing an invoice number and issue date after the first insert with updatable = false.
  • Pointing a field at a legacy column whose name does not follow snake_case.

Frequently asked questions

Does @Column(nullable = false) stop null values being saved?

Not by itself in a schema you manage with Flyway. It shapes generated DDL; the NOT NULL in your migration is what rejects the row. Validate the request with @NotNull or @NotBlank so the client gets a 400 instead.

Do I need @Column on every field?

No. Every persistent field is mapped automatically to a snake_case column. Add @Column only to change the name, add DDL details or make the column read-only.

Why does changing a field with updatable = false not save?

Hibernate leaves that column out of UPDATE statements, so the change stays in memory only. It is not an error; the value in the database simply keeps its original value.

More examples

The migration that really enforces the rules

With Flyway in charge, this script is the source of truth. The @Column attributes mirror it so that ddl-auto=validate and anyone reading the entity see the same rules.

Example · sql
-- V3__invoices.sql
create table invoice (
    id             bigint primary key,
    invoice_number varchar(20)    not null unique,
    total          numeric(12, 2) not null check (total >= 0),
    currency       char(3)        not null,
    issued_at      timestamptz    not null,
    notes          varchar(2000)
);

Matching validation on the request

The request limits mirror the column sizes: Digits(integer = 10, fraction = 2) matches numeric(12, 2). Bad input becomes a 400 with field errors instead of a database exception.

Example · java
import jakarta.validation.Valid;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

record CreateInvoiceRequest(
        @NotBlank @Size(max = 20) String number,
        @NotNull @DecimalMin("0.00") @Digits(integer = 10, fraction = 2) BigDecimal total,
        @NotBlank @Size(min = 3, max = 3) String currency,
        @Size(max = 2000) String notes) { }

@RestController
public class InvoiceController {

    @PostMapping("/invoices")
    public ResponseEntity<Void> create(@Valid @RequestBody CreateInvoiceRequest request) {
        // too-long values are rejected here with a 400, not by the database with a 500
        return ResponseEntity.accepted().build();
    }
}

A database-computed, read-only column

insertable = false and updatable = false keep Hibernate from writing a value the database computes. Note the field is only refreshed when the entity is loaded again, not right after save().

Example · java
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class Customer {
    @Id
    private Long id;

    private String firstName;
    private String lastName;

    // generated column maintained by the database; Hibernate never writes it
    @Column(name = "search_name", insertable = false, updatable = false)
    private String searchName;

    protected Customer() { }

    public String getSearchName() { return searchName; }
}

Discussion

  • Be the first to comment on this lesson.