{"record":{"id":"76668c3d8e655b81","repo":"spring-projects/spring-security","slug":"principal-is-not-assignable-to-parameter-g","errorCode":null,"errorMessage":"principal + \" is not assignable to \" + parameter.getParameterType()","messagePattern":"principal \\+ \" is not assignable to \" \\+ parameter\\.getParameterType\\(\\)","errorType":"exception","errorClass":"ClassCastException","httpStatus":null,"severity":"error","filePath":"web/src/main/java/org/springframework/security/web/bind/support/AuthenticationPrincipalArgumentResolver.java","lineNumber":108,"sourceCode":"public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {\n\n\t@Override\n\tpublic boolean supportsParameter(MethodParameter parameter) {\n\t\treturn findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;\n\t}\n\n\t@Override\n\tpublic @Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,\n\t\t\tNativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {\n\t\tAuthentication authentication = SecurityContextHolder.getContext().getAuthentication();\n\t\tif (authentication == null) {\n\t\t\treturn null;\n\t\t}\n\t\tObject principal = authentication.getPrincipal();\n\t\tif (principal != null && !parameter.getParameterType().isAssignableFrom(principal.getClass())) {\n\t\t\t@Nullable AuthenticationPrincipal authPrincipal = findMethodAnnotation(AuthenticationPrincipal.class, parameter);\n\t\t\tif (authPrincipal != null && authPrincipal.errorOnInvalidType()) {\n\t\t\t\tthrow new ClassCastException(principal + \" is not assignable to \" + parameter.getParameterType());\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t\treturn principal;\n\t}\n\n\t/**\n\t * Obtains the specified {@link Annotation} on the specified {@link MethodParameter}.\n\t * @param annotationClass the class of the {@link Annotation} to find on the\n\t * {@link MethodParameter}\n\t * @param parameter the {@link MethodParameter} to search for an {@link Annotation}\n\t * @return the {@link Annotation} that was found or null.\n\t */\n\tprivate <T extends Annotation> @Nullable T findMethodAnnotation(Class<T> annotationClass,\n\t\t\tMethodParameter parameter) {\n\t\tT annotation = parameter.getParameterAnnotation(annotationClass);\n\t\tif (annotation != null) {\n\t\t\treturn annotation;","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/web/src/main/java/org/springframework/security/web/bind/support/AuthenticationPrincipalArgumentResolver.java#L90-L126","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n@GetMapping(\"/me\")\npublic MeDto me(@AuthenticationPrincipal(errorOnInvalidType = true) MyUser user) { ... }\n// after\n@GetMapping(\"/me\")\npublic MeDto me(@AuthenticationPrincipal MyUser user) { // null when type differs\n    if (user == null) throw new AccessDeniedException(\"unexpected principal type\");\n    ...\n}","handlingStrategy":"type-guard","validationCode":"Authentication auth = SecurityContextHolder.getContext().getAuthentication();\nif (auth == null || !(auth.getPrincipal() instanceof MyUser)) {\n    throw new AccessDeniedException(\"Expected principal of type MyUser, got \"\n        + (auth == null ? \"anonymous\" : auth.getPrincipal().getClass().getSimpleName()));\n}\n","typeGuard":"boolean hasPrincipalOfType(Class<?> expected) {\n    Authentication a = SecurityContextHolder.getContext().getAuthentication();\n    return a != null && expected.isInstance(a.getPrincipal());\n}","tryCatchPattern":"try {\n    return resolver.resolveArgument(parameter, null, webRequest, binderFactory);\n} catch (ClassCastException e) {\n    throw new AccessDeniedException(\"Principal type mismatch for @AuthenticationPrincipal: \" + e.getMessage());\n}","preventionTips":["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"],"tags":["spring-security","spring-mvc","classcastexception","argument-resolver","type-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}