Field & Column Mappings
Field & Column Mappings
Previous lessons showed you how to map an entity class and choose a primary key strategy. Most of your actual work, however, lives in the fields between those primary keys: dates, status codes, large text blobs, and the dozens of scalar values that make up a real domain object. Hibernate handles all of them through a small set of annotations — @Temporal, @Enumerated, and @Lob — together with the foundational @Column. This lesson examines each in depth, explains the trade-offs, and points out the gotchas that trip up developers in production.
@Column — Controlling the Physical Column
Without @Column, Hibernate derives the column name from the field name using its naming strategy (by default, camelCase → snake_case in Spring Boot). You override that, and add constraints the DDL generator should emit, with @Column:
Key attributes: name sets the column name, nullable/unique control DDL constraints, length caps VARCHAR, and precision/scale govern DECIMAL columns. columnDefinition lets you pass raw SQL type text when you need database-specific behaviour.
nullable = false and unique = true only affect schema generation (spring.jpa.hibernate.ddl-auto=create). They do NOT cause Hibernate to validate values at runtime. For runtime enforcement use Bean Validation (@NotNull, @Email) from jakarta.validation.
@Enumerated — Storing Java Enums
Suppose your order has a status enum:
Without any annotation, Hibernate persists the enum's ordinal (0, 1, 2 …) as an INTEGER. That is almost always a mistake.
EnumType.STRING stores the constant name ('PENDING', 'SHIPPED' …), which survives reordering and makes the database rows human-readable. Always use EnumType.STRING.
In Hibernate 6 (Spring Boot 3) there is a further option: mapping an enum to a database-native ENUM type using @JdbcType or a custom AttributeConverter. For most projects, EnumType.STRING with a bounded VARCHAR is the pragmatic choice because it is portable across databases and fully readable in SQL queries.
@Temporal — Date and Time Fields
In Hibernate 6 with Spring Boot 3 you should use the java.time types introduced in Java 8. Hibernate maps them automatically, with no annotation needed:
LocalDate → DATE, LocalDateTime → DATETIME/TIMESTAMP, OffsetDateTime / Instant → timezone-aware TIMESTAMP. No additional annotation is required.
The legacy @Temporal annotation was designed for the old java.util.Date and java.util.Calendar types, which carry timezone ambiguity and mutable state. You may still encounter it in legacy codebases:
java.time types. They are immutable, timezone-explicit, and map to the correct SQL types without any annotation. Use Instant or OffsetDateTime for any timestamp that must be timezone-aware (e.g., audit fields). Use LocalDate for calendar dates that have no time component (birthdays, expiry dates).
A common pattern for audit timestamps uses Spring Data JPA lifecycle callbacks alongside java.time:
@Lob — Large Object Columns
@Lob signals Hibernate to use the JDBC Large Object API for the column. On a String field it maps to CLOB (Character Large Object); on a byte[] field it maps to BLOB (Binary Large Object).
SELECT a FROM Article a), every row will stream its entire large column from the database, potentially transferring megabytes for a page that only needs titles. Two mitigations: (1) Use @Basic(fetch = FetchType.LAZY) to defer loading the LOB until the field is actually accessed — though this requires build-time bytecode instrumentation. (2) More reliably, move the large field into a separate entity with a @OneToOne lazy relationship and only join it when needed. For user-uploaded files, storing a file path or an object-storage URL in a normal VARCHAR column and serving the binary from S3/GCS is almost always the better architecture.
In Hibernate 6, the type mapping for @Lob on String can behave differently across databases. PostgreSQL, for instance, may map it to TEXT rather than CLOB. If you only need large text storage on PostgreSQL you can simply annotate with @Column(columnDefinition = "TEXT") and omit @Lob entirely, which avoids the CLOB streaming overhead.
Custom Type Mappings with AttributeConverter
When none of the built-in annotations cover your type — say, a List<String> of tags stored as a comma-separated column, or a Money value object — use AttributeConverter:
Apply it on the field:
jsonb, MySQL 5.7+ json), store structured sub-data as JSON using Hibernate's @JdbcTypeCode(SqlTypes.JSON) instead of hand-rolling a CSV converter. JSON columns are indexable, queryable with native SQL functions, and far more flexible.
Summary
Use @Column to control the physical column name, nullability, and length. Store enums as strings with @Enumerated(EnumType.STRING) and never as ordinals. Prefer java.time types — which need no annotation — over the legacy @Temporal/java.util.Date approach. Reserve @Lob for genuine large objects and be aware of its eager-loading cost; consider storing binary assets outside the database. For custom types, AttributeConverter gives you full control with a clean separation between the Java and SQL representations.