spring-projects/spring-security · error · ClassCastException

<principal> is not assignable to <parameterType>

Error message

<principal> is not assignable to <parameterType>

What it means

In reactive controllers, the @AuthenticationPrincipal argument resolver evaluates a SpEL expression against the authenticated principal. If the resulting object is not assignable to the method parameter type and annotation.errorOnInvalidType() is true (the default), a ClassCastException is thrown. This fails fast when the controller declares a concrete principal type that the current authentication does not provide.

Source

Thrown at web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java:115

	private @Nullable Object resolvePrincipal(MethodParameter parameter, @Nullable Object principal) {
		AuthenticationPrincipal annotation = findMethodAnnotation(parameter);
		if (annotation == null) {
			// FIXME: Add test
			return null;
		}
		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 (isInvalidType(parameter, principal)) {
			if (annotation.errorOnInvalidType()) {
				throw new ClassCastException(principal + " is not assignable to " + parameter.getParameterType());
			}
			return null;
		}
		return principal;
	}

	private boolean isInvalidType(MethodParameter parameter, @Nullable Object principal) {
		if (principal == null) {
			return false;
		}
		Class<?> typeToCheck = parameter.getParameterType();
		boolean isParameterPublisher = Publisher.class.isAssignableFrom(parameter.getParameterType());
		if (isParameterPublisher) {
			ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
			Class<?> genericType = resolvableType.resolveGeneric(0);
			if (genericType == null) {
				return false;
			}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Declare the parameter as the type actually returned by the Authentication (e.g. OAuth2User or Jwt instead of UserDetails) and inspect it there.
  2. Set errorOnInvalidType = false on @AuthenticationPrincipal so a mismatch yields null instead of an exception, and handle the null case.
  3. Make the principal type consistent: configure a single Authentication implementation/UserDetailsService across the app.
  4. If using expression="...", verify the expression result type matches the parameter type before binding.

Example fix

// before
public Mono<String> me(@AuthenticationPrincipal UserDetails user) { ... }
// after
public Mono<String> me(@AuthenticationPrincipal OAuth2User user) {
    return Mono.just(user.getName());
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Verify principal type before relying on the annotation
Authentication auth = exchange.getPrincipal().block();
if (!(auth != null && auth.getPrincipal() instanceof MyUserDetails)) {
    throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}

Type guard

// Java instanceof guard
static boolean isMyUserDetails(Principal principal) {
    return principal instanceof Authentication a && a.getPrincipal() instanceof MyUserDetails;
}

Try / catch

try {
    return handlerMethod.invoke(...);
} catch (ClassCastException e) {
    log.error("Principal type mismatch: {}", e.getMessage());
    throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid principal type");
}

Prevention

When it happens

Trigger: A controller method declares e.g. @AuthenticationPrincipal UserDetails user, but the resolved principal (or a SpEL-derived value like expression="claims['sub']") is of a different type, with errorOnInvalidType defaulted to true.

Common situations: Switching token providers so the principal becomes a Jwt or OAuth2User instead of UserDetails; using expression attributes that produce Strings/claims objects; sharing controller code between apps with different Authentication implementations; custom UserDetailsService types mismatched after refactoring.

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/6b3095f623509852. Report an issue: GitHub.