spring-projects/spring-security · error · ClassCastException

principal + " is not assignable to " + parameter.getParamete

Error message

principal + " is not assignable to " + parameter.getParameterType()

What it means

The current org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver resolves @AuthenticationPrincipal parameters, optionally applying a SpEL expression to the principal. If the resulting principal (raw or expression-evaluated) is not assignable to the declared parameter type and the annotation's errorOnInvalidType is true, resolveArgument throws a ClassCastException naming the principal and the target parameter type.

Source

Thrown at web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java:135

		Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
		if (authentication == null) {
			return null;
		}
		Object principal = authentication.getPrincipal();
		AuthenticationPrincipal annotation = findMethodAnnotation(parameter);
		Assert.notNull(annotation, "@AuthenticationPrincipal is required. Call supportsParameter first.");
		String expressionToParse = annotation.expression();
		if (StringUtils.hasLength(expressionToParse)) {
			StandardEvaluationContext context = new StandardEvaluationContext();
			context.setRootObject(principal);
			context.setVariable("this", principal);
			context.setBeanResolver(this.beanResolver);
			Expression expression = this.parser.parseExpression(expressionToParse);
			principal = expression.getValue(context);
		}
		if (principal != null && !ClassUtils.isAssignable(parameter.getParameterType(), principal.getClass())) {
			if (annotation.errorOnInvalidType()) {
				throw new ClassCastException(principal + " is not assignable to " + parameter.getParameterType());
			}
			return null;
		}
		return principal;
	}

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return findMethodAnnotation(parameter) != null;
	}

	/**
	 * Sets the {@link BeanResolver} to be used on the expressions.
	 * @param beanResolver the {@link BeanResolver} to use
	 */
	public void setBeanResolver(BeanResolver beanResolver) {
		this.beanResolver = beanResolver;
	}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Verify the expression's evaluated type matches the parameter type (e.g. principal.claims['sub'] returns String vs Long) and correct the expression or parameter declaration.
  2. Remove errorOnInvalidType=true so a mismatch yields null instead of a 500 ClassCastException, and handle null explicitly.
  3. Unify principal types across all authentication mechanisms (custom UserDetails adopted by OAuth2/JWT login too).
  4. Add a test that authenticates through the real mechanism and invokes the controller to catch type drift early.

Example fix

// before
public OrderDto get(@AuthenticationPrincipal(errorOnInvalidType = true, expression = "attributes['sub']") MyUser user)
// after (expression returns a String, so accept String)
public OrderDto get(@AuthenticationPrincipal(expression = "attributes['sub']") String userId) { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth == null ? null : auth.getPrincipal();
if (principal != null && !ClassUtils.isAssignable(MyUser.class, principal.getClass())) {
    throw new AccessDeniedException("Principal " + principal.getClass() + " is not MyUser");
}

Type guard

static <T> T principalAs(Class<T> type) {
    Authentication a = SecurityContextHolder.getContext().getAuthentication();
    Object p = a == null ? null : a.getPrincipal();
    return type.isInstance(p) ? type.cast(p) : null;
}

Try / catch

try {
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
} catch (ClassCastException e) {
    log.warn("@AuthenticationPrincipal type mismatch (check expression result type): {}", e.getMessage());
    return null; // or map to 403 depending on policy
}

Prevention

When it happens

Trigger: A controller declares @AuthenticationPrincipal(expression = "...") CustomType param (or plain @AuthenticationPrincipal CustomType) with errorOnInvalidType=true, but the SecurityContext principal — or the SpEL expression's result — is of an unrelated runtime type, so ClassUtils.isAssignable fails.

Common situations: SpEL expressions returning the wrong property type (e.g. expression yielding a String while the parameter expects a Long or custom type); principal type changed after migrating from session login to JWT/OAuth2; method-security or tests injecting a different Authentication; copy-pasted controllers across apps with different UserDetails classes.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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