hibernate/hibernate-orm · error · UnbreakableUniqueCycleException
Unbreakable unique update cycle detected for SCC: %s
Error message
Unbreakable unique update cycle detected for SCC: %s
What it means
The graph-based flush planner detected a cycle made up only of UPDATE operations ordered by unique constraints - the classic one-to-one unique value swap - but no edge in the cycle is an explicit NULL_PATCHABLE_UNIQUE edge. Since every unique edge in the cycle is required (non-nullable), Hibernate cannot apply its NULL-then-patch strategy (temporarily null the unique column, run the updates, then patch the final value), so it throws UnbreakableUniqueCycleException rather than emit SQL that would violate the constraint.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/action/queue/internal/plan/CycleBreaker.java:111
final GraphEdge preferredOrderEdge = findPreferredOrderEdge(cycle);
if (preferredOrderEdge != null) {
preferredOrderEdge.setBroken(true);
continue;
}
// Unique-only UPDATE cycles occur when swapping unique values, for example
// one-to-one relationships. Required unique edges participate in this
// classification, but only explicit NULL_PATCHABLE_UNIQUE edges can install
// a NULL-then-patch update.
if ( isUniqueUpdateOrderingCycle( cycle ) ) {
final GraphEdge uniquePatchCandidate = findUniquePatchCandidate( cycle );
if ( uniquePatchCandidate != null ) {
uniquePatchCandidate.setBroken( true );
installPatchForEdge( uniquePatchCandidate );
}
else {
throw new UnbreakableUniqueCycleException(
"Unbreakable unique update cycle detected for SCC: " + describeScc( scc )
);
}
continue;
}
final GraphEdge chosen = chooseEdgeToBreak(cycle);
if (chosen == null) {
if (isDeleteOnlyCycle(cycle)) {
breakArbitraryDeleteEdge(cycle, deferrableConstraintMode);
continue;
}
final GraphEdge updateEdge = findUpdateEdge(cycle);
if (updateEdge != null) {
updateEdge.setBroken(true);
continue;
}View on GitHub (pinned to fad1729dce)
Solutions
- Make one side of the one-to-one nullable: drop optional=false / nullable=false on one end so the planner can NULL the unique value first and patch it after the swap.
- Model the association as unidirectional (unique FK owned by one side only) so no unique cycle exists.
- Split the swap into two steps with an intermediate flush: null one side, flush, then assign the new partner.
- On databases that support it, declare the unique constraint DEFERRABLE and enable Hibernate's deferrable constraint mode so the planner can break the edge.
Example fix
// before - both sides mandatory, swap is unbreakable @OneToOne(mappedBy = "a", optional = false) private B b; // after - inverse side nullable, planner can NULL-then-patch @OneToOne(mappedBy = "a") private B b;
Defensive patterns
Strategy: validation
Validate before calling
// never swap unique one-to-one partners in a single flush: sever one side first a.setOther(null); session.flush(); a.setOther(b2); b2.setOther(a);
Try / catch
try {
session.flush();
}
catch (HibernateException e) {
Throwable c = e;
while (c != null) {
if ("UnbreakableUniqueCycleException".equals(c.getClass().getSimpleName())) {
tx.rollback(); // retry with a two-step swap: null one side, flush, reassign
break;
}
c = c.getCause();
}
} Prevention
- Avoid optional=false / nullable=false on both sides of a bidirectional one-to-one
- Reassign one-to-one references in two steps with a flush between when partners are swapped
- On PostgreSQL, mark swap-heavy unique constraints DEFERRABLE and enable Hibernate's deferrable constraint mode
When it happens
Trigger: Swapping one-to-one references inside one un-flushed transaction (a.setOther(b2) plus b2.setOther(a), or reassigning both ends of a bidirectional one-to-one) where the unique FK columns are mapped optional=false / @JoinColumn(nullable=false) on both sides; any single-flush reassignment of NOT NULL unique foreign keys that forms a cycle.
Common situations: Bidirectional one-to-one with mandatory unique FKs on both tables; models migrated from the legacy action queue where hard-coded ordering happened to work; databases that cannot defer unique constraint checks (MySQL, MariaDB, SQL Server).
Related errors
- Unbreakable cycle detected for SCC: %s
- Unsupported JdbcOperation type: %s
- Composite key breakdown not yet implemented
- There are delayed insert actions before operation as cascade
- cannot recreate collection while filter is enabled: " + coll
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/8d9097473888582c.
Report an issue: GitHub.