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
- Declare the parameter as the type actually returned by the Authentication (e.g. OAuth2User or Jwt instead of UserDetails) and inspect it there.
- Set errorOnInvalidType = false on @AuthenticationPrincipal so a mismatch yields null instead of an exception, and handle the null case.
- Make the principal type consistent: configure a single Authentication implementation/UserDetailsService across the app.
- 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
- Keep one consistent Authentication/UserDetailsService type per app
- Use errorOnInvalidType=false plus null checks for tolerant resolution
- Add tests asserting the principal type your controllers expect
- Prefer interface types (OAuth2User, UserDetails) in controller signatures
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
- <securityContextResult> is not assignable to <parameterType>
- invalid_token
- principal + " is not assignable to " + parameter.getParamete
- principal + " is not assignable to " + parameter.getParamete
- AdviceMode {adviceMode} is not supported
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/6b3095f623509852.
Report an issue: GitHub.