{"record":{"id":"60fb5f5f19feb2a8","repo":"spring-projects/spring-security","slug":"cannot-delete-objectidentity-has-n-children","errorCode":null,"errorMessage":"Cannot delete '<objectIdentity>' (has <N> children)","messagePattern":"Cannot delete '<objectIdentity>' \\(has <N> children\\)","errorType":"exception","errorClass":"ChildrenExistException","httpStatus":null,"severity":"error","filePath":"acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java","lineNumber":292,"sourceCode":"\t@Override\n\tpublic void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException {\n\t\tAssert.notNull(objectIdentity, \"Object Identity required\");\n\t\tAssert.notNull(objectIdentity.getIdentifier(), \"Object Identity doesn't provide an identifier\");\n\t\tif (deleteChildren) {\n\t\t\tList<ObjectIdentity> children = findChildren(objectIdentity);\n\t\t\tif (children != null) {\n\t\t\t\tfor (ObjectIdentity child : children) {\n\t\t\t\t\tdeleteAcl(child, true);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (!this.foreignKeysInDatabase) {\n\t\t\t\t// We need to perform a manual verification for what a FK would normally\n\t\t\t\t// do. We generally don't do this, in the interests of deadlock management\n\t\t\t\tList<ObjectIdentity> children = findChildren(objectIdentity);\n\t\t\t\tif (children != null) {\n\t\t\t\t\tthrow new ChildrenExistException(\n\t\t\t\t\t\t\t\"Cannot delete '\" + objectIdentity + \"' (has \" + children.size() + \" children)\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tLong oidPrimaryKey = retrieveObjectIdentityPrimaryKey(objectIdentity);\n\t\tif (oidPrimaryKey == null) {\n\t\t\tthrow new NotFoundException(\"Object identity not found: \" + objectIdentity);\n\t\t}\n\n\t\t// Delete this ACL's ACEs in the acl_entry table\n\t\tdeleteEntries(oidPrimaryKey);\n\n\t\t// Delete this ACL's acl_object_identity row\n\t\tdeleteObjectIdentity(oidPrimaryKey);\n\n\t\t// Clear the cache\n\t\tthis.aclCache.evictFromCache(objectIdentity);","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java#L274-L310","documentation":"JdbcMutableAclService.deleteAcl refuses to delete an ACL that still has child ACLs (children whose acl_object_identity.parent equals the target). When foreignKeysInDatabase is false (no DB-level FK cascade constraints), the service manually checks findChildren and throws ChildrenExistException if any exist. This prevents orphaning child ACLs that inherit from the deleted parent.","triggerScenarios":"Calling deleteAcl(objectIdentity, deleteChildren=false) on an ACL that is the parent of other ACLs while the database schema lacks the foreign keys (foreignKeysInDatabase=false), e.g. the default sample schema without FK definitions.","commonSituations":"Deleting a parent domain object (e.g. a folder) whose children (files) have their own ACLs; running against the standard ACL schema where acl_object_identity has no FK constraints; cascading entity deletes in application code that skip ACL child cleanup.","solutions":["Delete child ACLs first (recursively via findChildren), then delete the parent.","Call deleteAcl with deleteChildren=true to cascade the deletion.","Add proper foreign key constraints to acl_object_identity (parent FK with ON DELETE CASCADE) and set foreignKeysInDatabase=true so the DB handles it.","Catch org.springframework.security.acls.model.ChildrenExistException and surface a 'remove children first' condition to the caller."],"exampleFix":"// before\nmutableAclService.deleteAcl(oid, false);\n// after\nmutableAclService.deleteAcl(oid, true); // cascade-delete children ACLs","handlingStrategy":"try-catch","validationCode":"List<ObjectIdentity> children = ((JdbcMutableAclService) aclService).findChildren(oid);\nif (children != null && !children.isEmpty() && !deleteChildren) { /* resolve first */ }","typeGuard":"null","tryCatchPattern":"try {\n    mutableAclService.deleteAcl(oid, false);\n} catch (ChildrenExistException e) {\n    // delete children recursively first, then retry\n}","preventionTips":["Add FK constraints with ON DELETE CASCADE and set foreignKeysInDatabase=true","Delete child ACLs before parent in cleanup jobs","Prefer deleteAcl(oid, true) when children need not be preserved"],"tags":["spring-security","acl","hierarchy","delete"],"backgroundTag":"invalid-state-transition","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}