@Column
Customises how an entity field maps to its column: name, nullability, length, precision, uniqueness, and whether Hibernate includes it in INSERT and UPDATE.
@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
| Parameter | Type | Description |
|---|---|---|
$nameoptional | String= field name | Column name. Defaults to the field name, converted to snake_case by Spring Boot's naming strategy. |
$nullableoptional | boolean= true | Whether the generated DDL allows NULL. |
$lengthoptional | int= 255 | Column length for string columns in generated DDL. |
$precisionoptional | int= 0 | Total digits for decimal columns (BigDecimal). |
$scaleoptional | int= 0 | Digits after the decimal point for decimal columns. |
$uniqueoptional | boolean= false | Adds a single-column unique constraint in generated DDL. |
$insertableoptional | boolean= true | Whether Hibernate includes the column in INSERT statements. |
$updatableoptional | boolean= true | Whether Hibernate includes the column in UPDATE statements. |
$columnDefinitionoptional | String= "" | Raw SQL type fragment used in generated DDL. Ties the mapping to one database. |
$tableoptional | String= primary table | The secondary table that holds the column. |
$commentoptional | String= "" | Column comment for generated DDL. |
$checkoptional | CheckConstraint[]= {} | Check constraints for generated DDL. |
$optionsoptional | String= "" | Extra SQL fragment appended to the column definition in generated DDL. |
$secondPrecisionoptional | int= provider default | Fractional-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 theCREATE TABLEHibernate generates. When Flyway owns the schema, they are documentation; the database constraints in your migration are what actually reject bad data. - Runtime:
name,insertableandupdatablechange 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
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import java.math.BigDecimal;
import java.time.Instant;
public class Invoice {
private Long id;
(name = "invoice_number", nullable = false, unique = true, length = 20, updatable = false)
private String number;
(nullable = false, precision = 12, scale = 2)
private BigDecimal total;
(nullable = false, length = 3)
private String currency;
(name = "issued_at", nullable = false, updatable = false)
private Instant issuedAt;
(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.
-- 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.
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().
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