ValueClassEquals
Non-record concrete value classes should explicitly override equals and hashCode; prefer a value record, or delegate to super.equals and super.hashCode if state-wise identity comparison is intended

Severity
WARNING
Tags
FragileCode

The problem

Non-record concrete value classes (JEP 401) should explicitly override equals and hashCode.

Unlike a value record—which automatically synthesizes component-wise equals and hashCode methods that call .equals() and .hashCode() on reference components—a non-record value class that omits equals and hashCode inherits Object.equals (return this == obj;) and Object.hashCode (System.identityHashCode(this)).

For a value class, Object.equals and this == obj perform a state-wise identity comparison of the class’s fields:

How to fix

If the class is a simple data carrier whose equality is defined by its components, prefer a value record:

value record UserId(String id) {}

If the class requires encapsulation (such as private fields, normalization, or defensive copies), explicitly implement equals and hashCode:

value class UserId {
  private final String id;

  UserId(String id) {
    this.id = id;
  }

  @Override
  public boolean equals(Object obj) {
    return obj instanceof UserId other && Objects.equals(id, other.id);
  }

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

If all fields of the value class are primitives or enums and state-wise comparison via Object.equals / Object.hashCode is genuinely intended, delegate explicitly so that the choice is clear to readers:

value class Meters {
  private final int value;

  Meters(int value) {
    this.value = value;
  }

  @Override
  public boolean equals(Object obj) {
    return super.equals(obj);
  }

  @Override
  public int hashCode() {
    return super.hashCode();
  }
}

Suppression

Suppress false positives by adding the suppression annotation @SuppressWarnings("ValueClassEquals") to the enclosing element.