BUG?DDL Generation: Name Collision between Top-Level Field and Field in Nested @Embeddable Mapped to JSON Causes Column Omission

Environment:

  • Hibernate ORM version: 6.6.8.Final
  • JPA provider: Hibernate
  • Spring Boot version: 3.4.3
  • Spring Data JPA version: (Managed via Spring Boot 3.4.3)
  • Database: PostgreSQL 17.0
  • JDBC Driver: PostgreSQL JDBC Driver 42.7.5
  • JDK version: 21.0.4
  • Operating System: Windows 11

Description:

When using hibernate.hbm2ddl.auto=create-drop, Hibernate fails to generate the DDL column for a top-level entity field if there is a field with the exact same name inside a nested @Embeddable structure that is itself mapped to a JSON-type column on the main entity table.

In my specific case:

  1. I have an entity Order with a field private Status status; mapped to @Column(name = "status") @Enumerated(EnumType.STRING).
  2. The Order entity also has a field private LogisticsInfo logisticsInfo; mapped with @JdbcTypeCode(SqlTypes.JSON) @Column(name = "logistics_info", columnDefinition = "jsonb").
  3. LogisticsInfo is an @Embeddable class containing a field private LogisticsItem email;.
  4. LogisticsItem is also an @Embeddable class and it contains a field named private LogisticsStatus status;.

During application startup with ddl-auto: create-drop, Hibernate’s metadata processing seems to encounter a naming conflict between Order.status and the property path Order.logisticsInfo.email.status. Although logisticsInfo is mapped to a single JSONB column, the internal field name status appears to interfere with the DDL generation for the top-level Order.status field.

The generated CREATE TABLE orders statement omits the definition for the status column entirely. This subsequently causes runtime PSQLException: ERROR: column "status" of relation "orders" does not exist when attempting to persist an Order entity.

Debug logs during startup showed messages like org.hibernate.mapping.BasicValue : Skipping column re-registration: orders.status when processing the Order.status field, strongly suggesting a naming conflict during metadata processing prevented the column from being correctly registered for DDL generation.

A simplified test entity (SimpleStatusTestEntity) with only an ID and a status field generated the correct DDL, confirming the base configuration and Hibernate DDL generation mechanism were working correctly in isolation. The issue appears specifically related to the combination of a top-level field and a same-named field within a nested Embeddable mapped to JSON.

Steps to Reproduce:

  1. Define necessary Enums:
    // Example Enums
    enum Status { NORMAL, HIDDEN, DELETED }
    enum LogisticsStatus { Pending, Failed, Success }
    enum LogisticsType { Email }
    
  2. Define the nested Embeddables:
    import jakarta.persistence.*;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    //... other imports
    
    @Getter @Setter @NoArgsConstructor @Embeddable
    public static class LogisticsInfo {
        private LogisticsItem email;
        // Potentially other fields
    }
    
    @Getter @Setter @NoArgsConstructor @Embeddable
    public static class LogisticsItem {
        private LogisticsType type;
        private LogisticsStatus status; // <-- Field name conflict
        private String content;
    
        public LogisticsItem(String email) {
            this.type = LogisticsType.Email;
            this.status = LogisticsStatus.Pending; // <-- Field name conflict
            this.content = email;
        }
    }
    
  3. Define the main Entity with the conflict:
    import dev.w0fv1.uclient.value.Status;
    import jakarta.persistence.*;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    import org.hibernate.annotations.JdbcTypeCode;
    import org.hibernate.type.SqlTypes;
    //... other imports
    
    @Getter @Setter @NoArgsConstructor @Entity @Table(name = "orders")
    public class Order { // Simplified
        @Id @GeneratedValue
        private Long id;
    
        // <<< Top-level field >>>
        @Column(name = "status")
        @Enumerated(EnumType.STRING)
        private Status status = Status.NORMAL;
    
        // <<< Embeddable mapped to JSON >>>
        @JdbcTypeCode(SqlTypes.JSON)
        @Column(name = "logistics_info", columnDefinition = "jsonb")
        private LogisticsInfo logisticsInfo = new LogisticsInfo();
    
        // Other necessary fields (e.g., Zone, Product, User FKs) for context
        // ...
    }
    
  4. Configure application.yml (or .properties):
    spring:
      jpa:
        hibernate:
          ddl-auto: create-drop
        show-sql: true
      datasource:
        url: jdbc:postgresql://localhost:5432/yourdb
        username: youruser
        password: yourpassword
        driver-class-name: org.postgresql.Driver
    
  5. Start the Spring Boot application.
  6. Observe the application startup logs. The generated CREATE TABLE orders ... statement will be missing the status column definition.
  7. (Optional) Attempt to save an Order entity via its repository. This will trigger the PSQLException.

Expected Behavior:

The generated CREATE TABLE orders (...) statement should include a column definition for the top-level status field, respecting the @Column and @Enumerated annotations, e.g.:
..., status varchar(255) check (status in ('NORMAL','HIDDEN','DELETED')), ...

Actual Behavior:

The generated CREATE TABLE orders (...) statement completely omits the status column. Example DDL generated (from logs):

create table orders (is_anonymous boolean not null, paid_price numeric(38,2) not null, payment_status smallint not null check (payment_status between 0 and 2), quantity integer not null, total_price numeric(38,2) not null, created_time timestamp(6) with time zone, id bigint not null, product_id bigint not null, updated_time timestamp(6) with time zone, zone_id bigint not null, title varchar(256), uuid varchar(256) unique, product_type varchar(255) not null check (product_type in ('Virtual','VirtualSubscription','VirtualAction','Tangible','Package','Service')), recipient_id varchar(255) not null, user_id varchar(255) not null, delivery_note jsonb, delivery_snapshot jsonb, logistics_info jsonb, primary key (id))

(Notice the absence of the status column definition).

Workaround:

Renaming the conflicting field inside the nested @Embeddable (LogisticsItem.status to LogisticsItem.logisticsStatus) resolves the issue, and the DDL for orders.status is generated correctly.

Relevant Logs:

During metadata processing with the name conflict present, the following log line appeared when processing the Order.status field:

DEBUG ... org.hibernate.mapping.BasicValue : Skipping column re-registration: orders.status

Disclaimer:

My primary language is not English, and this summary was partially generated with AI assistance from logs and debugging. I haven’t had the time to confirm if this issue persists in the very latest snapshot/release builds or if it has already been fixed. I’m reporting this based on version 6.6.8.Final. If this is confirmed as a bug in Hibernate, I hope this report helps in getting it fixed. If this is intended behavior, an issue with my configuration, or already fixed, please feel free to close this issue with an explanation.

Thanks @zepeng_liu for bringing this up. This definitely sounds like a bug, so I would kindly ask you to please try to create a reproducer with our test case template and if you are able to reproduce the issue, create a new ticket in our issue tracker and attach that reproducer.