{"record":{"id":"b647a8ecc3adf1be","repo":"hibernate/hibernate-orm","slug":"unbreakable-cycle-detected-for-scc-s","errorCode":null,"errorMessage":"Unbreakable cycle detected for SCC: %s","messagePattern":"Unbreakable cycle detected for SCC: (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/action/queue/internal/plan/CycleBreaker.java","lineNumber":132,"sourceCode":"\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfinal GraphEdge chosen = chooseEdgeToBreak(cycle);\n\t\t\tif (chosen == null) {\n\t\t\t\tif (isDeleteOnlyCycle(cycle)) {\n\t\t\t\t\tbreakArbitraryDeleteEdge(cycle, deferrableConstraintMode);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tfinal GraphEdge updateEdge = findUpdateEdge(cycle);\n\t\t\t\tif (updateEdge != null) {\n\t\t\t\t\tupdateEdge.setBroken(true);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (!hasAnyBreakableEdge(cycle)) {\n\t\t\t\t\tthrow new IllegalStateException(\"Unbreakable cycle detected for SCC: \" + describeScc(scc));\n\t\t\t\t}\n\n\t\t\t\tfinal GraphEdge deferrableEdge = findEffectivelyDeferredEdge(cycle, deferrableConstraintMode);\n\t\t\t\tif (deferrableEdge != null) {\n\t\t\t\t\tdeferrableEdge.setBroken(true);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Break a non-DELETE edge as last resort.\n\t\t\t\tfinal GraphEdge nonDeleteEdge = findNonDeleteEdge(cycle);\n\t\t\t\tif (nonDeleteEdge != null) {\n\t\t\t\t\tnonDeleteEdge.setBroken(true);\n\t\t\t\t\t// Only explicit patchable edges actually install a patch; required\n\t\t\t\t\t// edges return null from GraphEdge#getPatchCycleType().\n\t\t\t\t\tif (nonDeleteEdge.getPatchNode() != null &&\n\t\t\t\t\t\tnonDeleteEdge.getPatchNode().group().kind() == MutationKind.INSERT) {\n\t\t\t\t\t\tinstallPatchForEdge(nonDeleteEdge);\n\t\t\t\t\t}","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/action/queue/internal/plan/CycleBreaker.java#L114-L150","documentation":"While making the flush dependency graph schedulable, CycleBreaker found a cycle with no edge it is allowed to break: no preferred-order edge, no update edge, and hasAnyBreakableEdge() reports nothing breakable at all. Every dependency in the cycle is a required (non-nullable, non-deferrable) FK/unique constraint, so any execution order would violate at least one constraint at flush time. Hibernate refuses to schedule doomed SQL and throws this IllegalStateException instead.","triggerScenarios":"Inserting or updating entities that reference each other through NOT NULL foreign keys in one transaction, e.g. @ManyToOne(optional=false) on both directions of A<->B, or a self-referential entity with a mandatory parent; schemas where FKs previously were deferrable but no longer are.","commonSituations":"Bidirectional @ManyToOne/@OneToOne marked optional=false on both sides; schema export produced NOT NULL FK columns on both tables; migrating from the legacy action queue whose ordering worked by accident; MySQL/MariaDB which lack deferred FK checks.","solutions":["Make at least one association in the cycle nullable (remove optional=false / nullable=false) so the planner can apply NULL-then-patch.","Restructure the model to break the cycle: make one direction unidirectional or move the FK to a nullable join table.","Assign in phases: persist A with the reference null, flush, then set B.a / A.b and flush again.","On PostgreSQL/Oracle, declare the FKs DEFERRABLE INITIALLY DEFERRED and enable Hibernate's deferrable constraint mode so edges in the cycle can be broken."],"exampleFix":"// before - mutual NOT NULL FKs, unbreakable cycle\n@Entity class A { @ManyToOne(optional = false) B b; }\n@Entity class B { @ManyToOne(optional = false) A a; }\n// after - one side may be temporarily null\n@Entity class A { @ManyToOne(optional = false) B b; }\n@Entity class B { @ManyToOne A a; }","handlingStrategy":"validation","validationCode":"// phase inserts so the NOT NULL cycle never exists within one flush\nA a = new A();\nB b = new B();\na.setB(b);          // B.a stays null for now\nsession.persist(a);\nsession.persist(b);\nsession.flush();\nb.setA(a);          // close the cycle after both rows exist","typeGuard":null,"tryCatchPattern":"try {\n    session.flush();\n}\ncatch (HibernateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"Unbreakable cycle detected for SCC\")) {\n        tx.rollback(); // reapply changes in phases with intermediate flushes\n    }\n    else throw e;\n}","preventionTips":["Never map both directions of a bidirectional association as optional=false","Use DEFERRABLE INITIALLY DEFERRED constraints for genuinely cyclic schemas","Add integration tests that mutate cyclic object graphs inside one transaction"],"tags":["hibernate","flush","foreign-key","cycle","non-nullable","action-queue"],"backgroundTag":"cyclic-foreign-key-dependency","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}