Hibernate Entity Equals Policy

ID

java.hibernate_entity_equals_policy

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Java

Tags

hibernate, orm, reliability

Description

Reports @Entity classes whose equals(Object) or hashCode() methods reference the @Id field. The JPA identifier is null for unsaved entities and changes after persist(), breaking hash-based collections that hold entities across lifecycle boundaries.

Rationale

Hibernate entities go through a lifecycle: transient (no id), managed (assigned id), detached. If equals or hashCode depend on the @Id field, the entity’s hash changes when persisted, causing it to "disappear" from HashSet or HashMap:

@Entity
public class Order {
    @Id @GeneratedValue
    private Long id;

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Order)) return false;
        return Objects.equals(id, ((Order) o).id);  // Bug: id is null before persist
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);  // Bug: hash changes after persist
    }
}

Adding a transient Order to a Set, then persisting it, makes contains() return false for the same object because the hash changed.

Remediation

Use a stable, immutable business-key field (e.g., email, UUID, natural key) or omit equals/hashCode entirely (inherit Object identity):

@Entity
public class Order {
    @Id @GeneratedValue
    private Long id;

    private String orderNumber;  // stable business key

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Order)) return false;
        return Objects.equals(orderNumber, ((Order) o).orderNumber);
    }

    @Override
    public int hashCode() {
        return Objects.hash(orderNumber);
    }
}