spring-projects/spring-security · error · ChildrenExistException

Cannot delete '<objectIdentity>' (has <N> children)

Error message

Cannot delete '<objectIdentity>' (has <N> children)

What it means

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.

Source

Thrown at acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java:292

	@Override
	public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException {
		Assert.notNull(objectIdentity, "Object Identity required");
		Assert.notNull(objectIdentity.getIdentifier(), "Object Identity doesn't provide an identifier");
		if (deleteChildren) {
			List<ObjectIdentity> children = findChildren(objectIdentity);
			if (children != null) {
				for (ObjectIdentity child : children) {
					deleteAcl(child, true);
				}
			}
		}
		else {
			if (!this.foreignKeysInDatabase) {
				// We need to perform a manual verification for what a FK would normally
				// do. We generally don't do this, in the interests of deadlock management
				List<ObjectIdentity> children = findChildren(objectIdentity);
				if (children != null) {
					throw new ChildrenExistException(
							"Cannot delete '" + objectIdentity + "' (has " + children.size() + " children)");
				}
			}
		}

		Long oidPrimaryKey = retrieveObjectIdentityPrimaryKey(objectIdentity);
		if (oidPrimaryKey == null) {
			throw new NotFoundException("Object identity not found: " + objectIdentity);
		}

		// Delete this ACL's ACEs in the acl_entry table
		deleteEntries(oidPrimaryKey);

		// Delete this ACL's acl_object_identity row
		deleteObjectIdentity(oidPrimaryKey);

		// Clear the cache
		this.aclCache.evictFromCache(objectIdentity);

View on GitHub (pinned to 96852e8860)

Solutions

  1. Delete child ACLs first (recursively via findChildren), then delete the parent.
  2. Call deleteAcl with deleteChildren=true to cascade the deletion.
  3. Add proper foreign key constraints to acl_object_identity (parent FK with ON DELETE CASCADE) and set foreignKeysInDatabase=true so the DB handles it.
  4. Catch org.springframework.security.acls.model.ChildrenExistException and surface a 'remove children first' condition to the caller.

Example fix

// before
mutableAclService.deleteAcl(oid, false);
// after
mutableAclService.deleteAcl(oid, true); // cascade-delete children ACLs
Defensive patterns

Strategy: try-catch

Validate before calling

List<ObjectIdentity> children = ((JdbcMutableAclService) aclService).findChildren(oid);
if (children != null && !children.isEmpty() && !deleteChildren) { /* resolve first */ }

Type guard

null

Try / catch

try {
    mutableAclService.deleteAcl(oid, false);
} catch (ChildrenExistException e) {
    // delete children recursively first, then retry
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/60fb5f5f19feb2a8. Report an issue: GitHub.