spring-projects/spring-security · error · NotFoundException

Unable to locate ACL to update

Error message

Unable to locate ACL to update

What it means

updateObjectIdentity issues an UPDATE on acl_object_identity keyed by the ACL's id (acl.getId()). If the update affects zero rows (count != 1), the persisted ACL row doesn't exist or the in-memory ACL's id doesn't correspond to any row, so NotFoundException("Unable to locate ACL to update") is thrown. This is a row-count invariant check on the final persistence step of updateAcl.

Source

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

	 * passed MutableAcl object. Also will create an acl_sid entry if needed for the Sid
	 * that owns the MutableAcl.
	 * @param acl to modify (a row must already exist in acl_object_identity)
	 * @throws NotFoundException if the ACL could not be found to update.
	 */
	protected void updateObjectIdentity(MutableAcl acl) {
		Long parentId = null;
		if (acl.getParentAcl() != null) {
			Assert.isInstanceOf(ObjectIdentityImpl.class, acl.getParentAcl().getObjectIdentity(),
					"Implementation only supports ObjectIdentityImpl");
			ObjectIdentityImpl oii = (ObjectIdentityImpl) acl.getParentAcl().getObjectIdentity();
			parentId = retrieveObjectIdentityPrimaryKey(oii);
		}
		Assert.notNull(acl.getOwner(), "Owner is required in this implementation");
		Long ownerSid = createOrRetrieveSidPrimaryKey(acl.getOwner(), true);
		int count = this.jdbcOperations.update(this.updateObjectIdentity, parentId, ownerSid, acl.isEntriesInheriting(),
				acl.getId());
		if (count != 1) {
			throw new NotFoundException("Unable to locate ACL to update");
		}
	}

	/**
	 * Sets the query that will be used to retrieve the identity of a newly created row in
	 * the <tt>acl_class</tt> table.
	 * @param classIdentityQuery the query, which should return the identifier. Defaults
	 * to <tt>call identity()</tt>
	 */
	public void setClassIdentityQuery(String classIdentityQuery) {
		Assert.hasText(classIdentityQuery, "New classIdentityQuery query is required");
		this.classIdentityQuery = classIdentityQuery;
	}

	/**
	 * Sets the query that will be used to retrieve the identity of a newly created row in
	 * the <tt>acl_sid</tt> table.
	 * @param sidIdentityQuery the query, which should return the identifier. Defaults to

View on GitHub (pinned to 96852e8860)

Solutions

  1. Re-read the ACL via readAclById immediately before mutating and updating, rather than reusing long-lived/cached ACL instances.
  2. Ensure acl.getId() is the actual acl_object_identity PK from the database — never fabricate it.
  3. Catch NotFoundException, reload the ACL (or recreate via createAcl), and re-apply changes.
  4. Serialize permission/entry changes rather than MutableAcl instances if you must persist state between requests.

Example fix

// before
MutableAcl cached = cache.get(oid); // may hold stale id
applyChanges(cached);
mutableAclService.updateAcl(cached);
// after
MutableAcl fresh = (MutableAcl) aclService.readAclById(oid);
applyChanges(fresh);
mutableAclService.updateAcl(fresh);
Defensive patterns

Strategy: try-catch

Validate before calling

// reload instead of trusting a cached ACL
MutableAcl fresh = (MutableAcl) aclService.readAclById(oid); // throws if row gone

Type guard

null

Try / catch

try {
    mutableAclService.updateAcl(cachedAcl);
} catch (NotFoundException e) {
    MutableAcl fresh = (MutableAcl) aclService.readAclById(oid);
    applyAndSave(fresh);
}

Prevention

When it happens

Trigger: updateAcl called with an ACL whose getId() (database PK) is stale, fabricated, or points to a row deleted by another transaction; passing an ACL deserialized from cache/session after the underlying row was removed.

Common situations: Caching MutableAcl objects across requests while ACLs get deleted/recreated (new PK each time); manually constructed AclImpl with a guessed id; two nodes racing update/delete on the same ACL; DB cleaned up between serialization and update.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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