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 javax.persistencejakarta.persistence namespace change, the ORM 6 query-engine rewrite, the Hibernate Search 6+ Elasticsearch backend), so the official documentation above wins on any discrepancy.

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 native jsonb/json column type where the dialect supports one (PostgreSQL, others).

  • LOBs — @Lob on a String maps a CLOB/TEXT; on a byte[] a BLOB/bytea. Large LOBs are usually combined with FetchType.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 custom UserType implementation; 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).