Basic and Embeddable Types
|
This section documents Hibernate ORM 7.4.x (User Guide, Introduction, Query Language Guide, Data Repositories Guide), Jakarta Persistence 3.2, Hibernate Search 8.4.x, and the Hibernate Validator / Hibernate Reactive references — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production. Three older reference books were consulted as bibliography only while preparing these pages and are not the
primary or main source for any page. All three predate Jakarta Persistence 3.2 and Hibernate ORM 6/7 (the
This section’s bibliography lists the reference material consulted while preparing these pages. |
Not every mapped attribute is an association. This page covers scalar ("basic") attributes and value-object ("embeddable") attributes — the answer to the granularity half of the paradigm mismatch (see Architecture).
@Basic and column mapping
@Basic is implicit for any field/property of a recognized simple type (String, primitives and their
wrappers, java.time.*, byte[], enums); it rarely needs to be written explicitly except to set
optional = false or fetch = FetchType.LAZY (lazy basic loading requires bytecode enhancement to actually
take effect). @Column controls the mapped column’s name, nullability, length/precision, and uniqueness:
@Column(name = "isbn", nullable = false, length = 20, unique = true)
private String isbn;
@Column(precision = 12, scale = 2)
private BigDecimal price;
@Enumerated
public enum Status { DRAFT, PUBLISHED, RETRACTED }
@Enumerated(EnumType.STRING)
private Status status;
EnumType.ORDINAL (the default if @Enumerated is omitted) stores the enum’s declaration position as an
integer — compact, but silently corrupts existing data if the enum’s constants are ever reordered or one is
inserted in the middle. EnumType.STRING stores the constant’s name — self-describing and safe to reorder,
at the cost of a few more bytes per row. Prefer EnumType.STRING unless a measured storage/index-size
concern says otherwise.
AttributeConverter
For any Java type with no built-in mapping, or one that needs storing differently in the database than in
memory, @Converter implements AttributeConverter<X, Y>:
@Converter(autoApply = true)
public class MoneyConverter implements AttributeConverter<Money, Long> {
@Override
public Long convertToDatabaseColumn(Money money) {
return money == null ? null : money.toCents();
}
@Override
public Money convertToEntityAttribute(Long cents) {
return cents == null ? null : Money.ofCents(cents);
}
}
autoApply = true applies the converter to every attribute of type Money across every entity without
needing @Convert on each field; autoApply = false (the default) requires an explicit
@Convert(converter = MoneyConverter.class) per attribute. Converters are also the standard way to map a Java
type to JSON storage (converting to/from a String holding serialized JSON, paired with a native JSON column
type via @JdbcTypeCode(SqlTypes.JSON) where the dialect supports one).
@Embeddable / @Embedded
The direct answer to the granularity mismatch: a value type with no identity or lifecycle of its own, whose fields are inlined into the owning entity’s table.
@Embeddable
public class Address {
private String street;
private String city;
private String postalCode;
// equals()/hashCode() by value, getters/setters omitted
}
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Embedded
private Address billingAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "street", column = @Column(name = "shipping_street")),
@AttributeOverride(name = "city", column = @Column(name = "shipping_city")),
@AttributeOverride(name = "postalCode", column = @Column(name = "shipping_postal_code"))
})
private Address shippingAddress;
}
@AttributeOverride is required whenever the same @Embeddable type is used more than once on an entity
(here, billingAddress and shippingAddress would otherwise collide on the same column names). An
@Embeddable can itself contain further embeddables and basic/converted attributes, but not associations to
other entities that would need their own identity within it — for that, model it as a proper @Entity and use
an association instead.
JSON columns, LOBs, and mapping to UDTs
-
JSON — either an
AttributeConverter<T, String>to/from a text/JSON column (portable across databases), or@JdbcTypeCode(SqlTypes.JSON)for a nativejsonb/jsoncolumn type where the dialect supports one (PostgreSQL, others). -
LOBs —
@Lobon aStringmaps aCLOB/TEXT; on abyte[]aBLOB/bytea. Large LOBs are usually combined withFetchType.LAZY(requires bytecode enhancement) to avoid loading megabyte-scale content with every entity fetch. -
User-defined types (UDTs) — database-specific composite/array column types are reached via
@JdbcTypeCode/a customUserTypeimplementation; there is no portable JPA annotation for them since they are inherently dialect-specific.
Geospatial types with Hibernate Spatial
The org.hibernate.orm:hibernate-spatial module maps true geometric column types (a point, a polygon, a line
string) directly, and exposes spatial SQL functions in HQL/JPQL — distinct from, and complementary to,
Hibernate Search’s own
GeoPoint-based proximity search (see that section for how the two compare and when to reach for each).
Hibernate Spatial supports two geometry libraries — JTS (org.locationtech.jts.geom.) and Geolatte-geom
(org.geolatte.geom.) — and needs no special mapping annotation: declaring a property as either library’s
Geometry/Point type is enough for Hibernate Spatial to map it once the module is on the classpath:
@Entity
public class Store {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private org.locationtech.jts.geom.Point location; // mapped automatically, no annotation needed
}
Querying uses ordinary HQL with the spatial functions Hibernate Spatial adds — within, distance/
st_distance, intersects, and the rest of the function catalog the User Guide’s Spatial chapter documents in
full:
GeometryFactory geometryFactory = new GeometryFactory();
Point center = geometryFactory.createPoint(new Coordinate(-122.4194, 37.7749));
List<Store> nearby = entityManager.createQuery(
"SELECT s FROM Store s WHERE distance(s.location, :center) < :radiusMeters", Store.class)
.setParameter("center", center)
.setParameter("radiusMeters", 10_000.0)
.getResultList();
This requires a spatial-capable dialect for the target database — most commonly PostgreSQL with the PostGIS
extension enabled; consult the User Guide’s own database-by-database notes (linked below) for the exact dialect
and setup each supported database needs, since spatial SQL support varies significantly by vendor. Because these
are real geometric operations evaluated by the database engine itself (not an approximate, index-backed search),
prefer Hibernate Spatial over Hibernate Search’s GeoPoint proximity predicate whenever the query needs exact
geometry (polygon containment, precise distance/area calculations) rather than a fast "nearest N" search over a
large, frequently-queried dataset.
Date/time types and time zones
Map java.time types directly — LocalDate, LocalDateTime, LocalTime, Instant, OffsetDateTime,
ZonedDateTime — Hibernate 6+ has first-class support for all of them, no converter needed. The one
persistent gotcha is time-zone handling: a database TIMESTAMP column (without time zone) has no time-zone
information at all, so OffsetDateTime/ZonedDateTime values are normalized to a single reference zone
(hibernate.timezone.default_storage) before storage and reconstructed on read — know which storage
strategy is configured before assuming a round-tripped ZonedDateTime keeps its original offset. Prefer
Instant (an unambiguous point on the UTC timeline) for "when did this happen" audit-style columns, and
LocalDate/LocalDateTime for values that are deliberately zone-less (a birth date, a scheduled wall-clock
time).
@Nationalized and @Immutable
@Nationalized maps a String/Character/Clob to the database’s national character column types
(NVARCHAR/NCHAR/NCLOB) for full Unicode support on databases (notably SQL Server) whose non-national
VARCHAR columns are code-page-limited. @Immutable, applied to an entity or a collection, tells Hibernate the
state never changes after being persisted — it skips dirty-checking snapshots for it entirely, a small but
free performance win for genuinely append-only or reference data (lookup tables, event logs).
Links
-
Hibernate Search Fundamentals — Geospatial search — the index-backed proximity-search alternative to this page’s exact-geometry queries.