{"record":{"id":"6b3095f623509852","repo":"spring-projects/spring-security","slug":"principal-is-not-assignable-to-parametertype","errorCode":null,"errorMessage":"<principal> is not assignable to <parameterType>","messagePattern":"<principal> is not assignable to <parameterType>","errorType":"exception","errorClass":"ClassCastException","httpStatus":null,"severity":"error","filePath":"web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java","lineNumber":115,"sourceCode":"\n\tprivate @Nullable Object resolvePrincipal(MethodParameter parameter, @Nullable Object principal) {\n\t\tAuthenticationPrincipal annotation = findMethodAnnotation(parameter);\n\t\tif (annotation == null) {\n\t\t\t// FIXME: Add test\n\t\t\treturn null;\n\t\t}\n\t\tString expressionToParse = annotation.expression();\n\t\tif (StringUtils.hasLength(expressionToParse)) {\n\t\t\tStandardEvaluationContext context = new StandardEvaluationContext();\n\t\t\tcontext.setRootObject(principal);\n\t\t\tcontext.setVariable(\"this\", principal);\n\t\t\tcontext.setBeanResolver(this.beanResolver);\n\t\t\tExpression expression = this.parser.parseExpression(expressionToParse);\n\t\t\tprincipal = expression.getValue(context);\n\t\t}\n\t\tif (isInvalidType(parameter, principal)) {\n\t\t\tif (annotation.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\tprivate boolean isInvalidType(MethodParameter parameter, @Nullable Object principal) {\n\t\tif (principal == null) {\n\t\t\treturn false;\n\t\t}\n\t\tClass<?> typeToCheck = parameter.getParameterType();\n\t\tboolean isParameterPublisher = Publisher.class.isAssignableFrom(parameter.getParameterType());\n\t\tif (isParameterPublisher) {\n\t\t\tResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);\n\t\t\tClass<?> genericType = resolvableType.resolveGeneric(0);\n\t\t\tif (genericType == null) {\n\t\t\t\treturn false;\n\t\t\t}","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java#L97-L133","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npublic Mono<String> me(@AuthenticationPrincipal UserDetails user) { ... }\n// after\npublic Mono<String> me(@AuthenticationPrincipal OAuth2User user) {\n    return Mono.just(user.getName());\n}","handlingStrategy":"type-guard","validationCode":"// Verify principal type before relying on the annotation\nAuthentication auth = exchange.getPrincipal().block();\nif (!(auth != null && auth.getPrincipal() instanceof MyUserDetails)) {\n    throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);\n}","typeGuard":"// Java instanceof guard\nstatic boolean isMyUserDetails(Principal principal) {\n    return principal instanceof Authentication a && a.getPrincipal() instanceof MyUserDetails;\n}","tryCatchPattern":"try {\n    return handlerMethod.invoke(...);\n} catch (ClassCastException e) {\n    log.error(\"Principal type mismatch: {}\", e.getMessage());\n    throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, \"Invalid principal type\");\n}","preventionTips":["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"],"tags":["spring-security","reactive","webflux","classcastexception","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"}