keycloak/keycloak · error · IllegalArgumentException

Permission id must not be null

Error message

Permission id must not be null

What it means

Thrown by PolicyResource.update(UmaPermissionRepresentation) when the permission is non-null but getId() returns null. The permission ID is used directly in the URL path of the PUT request (policy endpoint + / + encodedId). Without it, the server cannot identify which policy to update.

Source

Thrown at authz/client/src/main/java/org/keycloak/authorization/client/resource/PolicyResource.java:90

        try {
            return callable.call();
        } catch (Exception cause) {
            return Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Error creating policy for resource [" + resourceId + "]", cause);
        }
    }

    /**
     * Updates an existing user-managed permission
     *
     * @param permission the permission to update
     */
    public void update(final UmaPermissionRepresentation permission) {
        if (permission == null) {
            throw new IllegalArgumentException("Permission must not be null");
        }

        if (permission.getId() == null) {
            throw new IllegalArgumentException("Permission id must not be null");
        }

        Callable<Void> callable = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                http.<Void>put(serverConfiguration.getPolicyEndpoint() + "/"+ encodePathAsIs(permission.getId()))
                        .authorizationBearer(pat.call())
                        .json(JsonSerialization.writeValueAsBytes(permission)).execute();
                return null;
            }
        };
        try {
            callable.call();
        } catch (Exception cause) {
            Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Error updating policy for resource [" + resourceId + "]", cause);
        }
    }

View on GitHub (pinned to 66c7e15a37)

Solutions

  1. Load the existing permission via findById(id) first to get its server-assigned ID, then modify and update
  2. If the ID is known from another source, set it: permission.setId(existingId) before calling update()

Example fix

// before
UmaPermissionRepresentation perm = new UmaPermissionRepresentation();
perm.setName("Updated Policy");
policyResource.update(perm); // throws — no id

// after
UmaPermissionRepresentation perm = policyResource.findById(existingPolicyId);
perm.setName("Updated Policy");
policyResource.update(perm);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure permission has an ID before updating
UmaPermissionRepresentation permission = loadPermission();
if (permission == null || permission.getId() == null) {
    throw new IllegalStateException(
        "Cannot update UMA permission without a server-assigned ID");
}
policyResource.update(permission);

Type guard

public static boolean hasValidPermissionId(UmaPermissionRepresentation permission) {
    return permission != null && permission.getId() != null;
}

Prevention

When it happens

Trigger: Constructing a new UmaPermissionRepresentation locally (which has no server-assigned ID) and calling update() on it. Loading a permission from an external/serialized source where the id field was not populated.

Common situations: Confusing create() semantics with update() — building a fresh permission object and trying to update it. JSON deserialization that omits or misnames the id field.

Related errors


AI-assisted analysis of keycloak/keycloak@66c7e15a37 (2026-08-14). Data as JSON: /api/errors/a58158e53d86dc60. Report an issue: GitHub.