{"record":{"id":"edfb512c71fbc49e","repo":"hs-web/hsweb-framework","slug":"error-tree-entity-cyclic-dependency","errorCode":"error.tree_entity_cyclic_dependency","errorMessage":"error.tree_entity_cyclic_dependency","messagePattern":"error\\.tree_entity_cyclic_dependency","errorType":"validation","errorClass":"ValidationException","httpStatus":400,"severity":"error","filePath":"hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/service/TreeSortServiceHelper.java","lineNumber":136,"sourceCode":"        for (E value : allData.values()) {\n            if (isRootNode(value) || value.getId() == null) {\n                continue;\n            }\n            childrenMapping\n                .computeIfAbsent(value.getParentId(), ignore -> new LinkedHashMap<>())\n                .put(value.getId(), value);\n        }\n    }\n\n    private void checkCyclicDependency() {\n        for (E value : readyToSave.values()) {\n            checkCyclicDependency(value, new LinkedHashSet<>());\n        }\n    }\n\n    private void checkCyclicDependency(E val, Set<PK> container) {\n        if (!container.add(val.getId())) {\n            throw new ValidationException(\"parentId\", \"error.tree_entity_cyclic_dependency\");\n        }\n        Map<PK, E> children = childrenMapping.get(val.getId());\n        if (MapUtils.isNotEmpty(children)) {\n            for (Map.Entry<PK, E> entry : children.entrySet()) {\n                checkCyclicDependency(entry.getValue(), container);\n            }\n        }\n    }\n\n    private Mono<Void> checkParentId() {\n\n        if (allData.isEmpty()) {\n            return Mono.empty();\n        }\n\n        Set<PK> readyToCheck = thisTime\n            .values()\n            .stream()","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/hs-web/hsweb-framework/blob/b2cfc85a57c70bf5b5cf6e7edae2d37102652ec8/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/service/TreeSortServiceHelper.java#L118-L154","documentation":"TreeSortServiceHelper validates tree-structured entities for cyclic parent/child references. During DFS (checkCyclicDependency), if a node ID is encountered a second time in the current path (container.add returns false), a ValidationException on the `parentId` field with code `error.tree_entity_cyclic_dependency` is thrown.","triggerScenarios":"Saving or re-sorting a tree entity set whose parentId links form a loop, e.g. A.parent=B and B.parent=A, or a node whose parentId chain returns to itself; triggered via the helper's validation entry that seeds checkCyclicDependency for each root.","commonSituations":"Bulk imports where child rows precede parents with swapped IDs; UI drag-and-drop allowing a node to be dropped into its own subtree; concurrent edits creating inconsistent parent pointers.","solutions":["Fix the offending entities so no parentId chain loops (each node's ancestor chain ends at a root).","In the UI, forbid dropping a node into itself or its descendants.","Add a pre-save integrity check that rejects cycles and reports the involved IDs."],"exampleFix":"// before\nA.setParentId(\"B\"); B.setParentId(\"A\"); // cycle\n// after\nA.setParentId(\"B\"); B.setParentId(null); // B is root","handlingStrategy":"try-catch","validationCode":"Set<PK> seen = new HashSet<>(); for (E e : list) { PK p = e.getParentId(); while (p != null) { if (!seen.add(p)) { throw new ValidationException(\"parentId\", \"cycle detected at \" + p); } p = parentOf(p); } }","typeGuard":null,"tryCatchPattern":"try { helper.checkCyclicDependency(list); } catch (ValidationException e) { if (\"error.tree_entity_cyclic_dependency\".equals(e.getCode())) { /* return 400 with detail */ } throw e; }","preventionTips":["Prevent selecting a node's own subtree as parent in the UI","Validate parent links before bulk save","Write integration tests for swap-parent operations"],"tags":["tree-structure","cyclic-dependency","validation","data-integrity"],"backgroundTag":"invalid-state-transition","analyzedSha":"b2cfc85a57c70bf5b5cf6e7edae2d37102652ec8","analyzedAt":"2026-09-13T09:05:02.172Z","contentChangedAt":"2026-09-13T09:05:02.172Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}