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 legacy org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver resolves @AuthenticationPrincipal controller parameters. When the authenticated principal's runtime type is not assignable to the declared parameter type and the annotation sets errorOnInvalidType=true, resolveArgument throws a ClassCastException naming the principal and target type; otherwise it quietly returns null.
Source
Thrown at web/src/main/java/org/springframework/security/web/bind/support/AuthenticationPrincipalArgumentResolver.java:108
public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
}
@Override
public @Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
}
Object principal = authentication.getPrincipal();
if (principal != null && !parameter.getParameterType().isAssignableFrom(principal.getClass())) {
@Nullable AuthenticationPrincipal authPrincipal = findMethodAnnotation(AuthenticationPrincipal.class, parameter);
if (authPrincipal != null && authPrincipal.errorOnInvalidType()) {
throw new ClassCastException(principal + " is not assignable to " + parameter.getParameterType());
}
return null;
}
return principal;
}
/**
* Obtains the specified {@link Annotation} on the specified {@link MethodParameter}.
* @param annotationClass the class of the {@link Annotation} to find on the
* {@link MethodParameter}
* @param parameter the {@link MethodParameter} to search for an {@link Annotation}
* @return the {@link Annotation} that was found or null.
*/
private <T extends Annotation> @Nullable T findMethodAnnotation(Class<T> annotationClass,
MethodParameter parameter) {
T annotation = parameter.getParameterAnnotation(annotationClass);
if (annotation != null) {
return annotation;View on GitHub (pinned to 96852e8860)
Solutions
- Ensure every authentication path in the app populates a SecurityContext whose principal implements/extends the controller's declared parameter type (use one shared UserDetails implementation).
- If the parameter should simply be empty on mismatch, remove errorOnInvalidType=true (default behavior returns null instead of throwing).
- Align test security setup so mock Authentication objects use the same principal type production code produces.
- Alternatively declare the parameter as Object or the common supertype and narrow manually with instanceof.
Example fix
// before
@GetMapping("/me")
public MeDto me(@AuthenticationPrincipal(errorOnInvalidType = true) MyUser user) { ... }
// after
@GetMapping("/me")
public MeDto me(@AuthenticationPrincipal MyUser user) { // null when type differs
if (user == null) throw new AccessDeniedException("unexpected principal type");
...
} Defensive patterns
Strategy: type-guard
Validate before calling
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || !(auth.getPrincipal() instanceof MyUser)) {
throw new AccessDeniedException("Expected principal of type MyUser, got "
+ (auth == null ? "anonymous" : auth.getPrincipal().getClass().getSimpleName()));
}
Type guard
boolean hasPrincipalOfType(Class<?> expected) {
Authentication a = SecurityContextHolder.getContext().getAuthentication();
return a != null && expected.isInstance(a.getPrincipal());
} Try / catch
try {
return resolver.resolveArgument(parameter, null, webRequest, binderFactory);
} catch (ClassCastException e) {
throw new AccessDeniedException("Principal type mismatch for @AuthenticationPrincipal: " + e.getMessage());
} Prevention
- Use a single UserDetails implementation across all authentication mechanisms (form, JWT, OAuth2)
- Don't set errorOnInvalidType=true unless a wrong principal type must fail hard
- Keep security test fixtures' principal types identical to production
- When mixing auth mechanisms, declare the parameter as the common supertype and narrow with instanceof
When it happens
Trigger: A controller method declares @AuthenticationPrincipal CustomUserDetails user, but the SecurityContext holds a different principal type (e.g. String username when no UserDetailsService is used, or a different UserDetails implementation from another auth mechanism) — with errorOnInvalidType=true on the annotation.
Common situations: Mixing authentication mechanisms (form login with one UserDetails type, OAuth/JWT login producing a different principal); switching to JWT where the principal becomes a String or Jwt; multiple UserDetailsService beans registered; tests that stub Authentication with the wrong principal type.
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
- principal + " is not assignable to " + parameter.getParamete
- securityContextResult + " is not assignable to " + parameter
- <principal> is not assignable to <parameterType>
- <securityContextResult> is not assignable to <parameterType>
- A Bean named mvcHandlerMappingIntrospector of type org.sprin
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/76668c3d8e655b81.
Report an issue: GitHub.