import java.util.HashSet; import java.util.Set; import lombok.Getter; import lombok.Setter; import lombok.ToString; import lombok.EqualsAndHashCode; @Getter @ToString @EqualsAndHashCode(onlyExplicitlyIncluded = true) public class EqualsAndHashCodeDemo_WithNaturalId { @Setter private Long id; /** * This is pseudo-final (not final, but not setters or other mutations) as a demo of how to handle this. * It's required this way by some librariies/frameworks/environments, e.g. JPA */ @EqualsAndHashCode.Include private String naturalUniqueIdentifier; public EqualsAndHashCodeDemo_WithNaturalId(String naturalUniqueIdentifier) { this.naturalUniqueIdentifier = naturalUniqueIdentifier; } public static void main(String[] args) { EqualsAndHashCodeDemo_WithNaturalId demo = new EqualsAndHashCodeDemo_WithNaturalId("Some uniq String"); Set<EqualsAndHashCodeDemo_WithNaturalId> set = new HashSet<>(); if (set.contains(demo)) { throw new RuntimeException("We shouldn't end here since the demo is not in the set, yet"); } set.add(demo); if (!set.contains(demo)) { throw new RuntimeException("We shouldn't end here either because the demo is in the set, now"); } demo.setId(123L); if (!set.contains(demo)) { throw new RuntimeException("We shouldn't end here because hashCode has NOT changed"); } } }