spring-projects/spring-security · error · ClassCastException

securityContextResult + " is not assignable to " + parameter

Error message

securityContextResult + " is not assignable to " + parameter.getParameterType()

What it means

Thrown by CurrentSecurityContextArgumentResolver when a @CurrentSecurityContext annotated parameter cannot receive the resolved security context object because its declared Java type is not assignable from the actual result's class. Spring Security throws this (as ClassCastException) only when errorOnInvalidType() is true on the annotation; otherwise it silently returns null.

Source

Thrown at web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java:170

		this.scanner = SecurityAnnotationScanners.requireUnique(CurrentSecurityContext.class, templateDefaults);
	}

	private @Nullable Object resolveSecurityContextFromAnnotation(MethodParameter parameter,
			CurrentSecurityContext annotation, SecurityContext securityContext) {
		Object securityContextResult = securityContext;
		String expressionToParse = annotation.expression();
		if (StringUtils.hasLength(expressionToParse)) {
			StandardEvaluationContext context = new StandardEvaluationContext();
			context.setRootObject(securityContext);
			context.setVariable("this", securityContext);
			context.setBeanResolver(this.beanResolver);
			Expression expression = this.parser.parseExpression(expressionToParse);
			securityContextResult = expression.getValue(context);
		}
		if (securityContextResult != null
				&& !parameter.getParameterType().isAssignableFrom(securityContextResult.getClass())) {
			if (annotation.errorOnInvalidType()) {
				throw new ClassCastException(
						securityContextResult + " is not assignable to " + parameter.getParameterType());
			}
			return null;
		}
		return securityContextResult;
	}

	/**
	 * Obtain the specified {@link Annotation} on the specified {@link MethodParameter}.
	 * @param parameter the {@link MethodParameter} to search for an {@link Annotation}
	 * @return the {@link Annotation} that was found or null.
	 */
	private @Nullable CurrentSecurityContext findMethodAnnotation(MethodParameter parameter) {
		if (this.useAnnotationTemplate) {
			return this.scanner.scan(parameter.getParameter());
		}
		CurrentSecurityContext annotation = parameter.getParameterAnnotation(this.annotationType);
		if (annotation != null) {

View on GitHub (pinned to 96852e8860)

Solutions

  1. Change the parameter's declared type to a supertype actually produced by the expression (e.g. Authentication, SecurityContext, Object) and downcast safely in the body
  2. Align the annotation's expression with the parameter type, e.g. @CurrentSecurityContext(expression = "authentication.principal") with parameter type matching the actual principal class
  3. If a null is acceptable on mismatch, set @CurrentSecurityContext(errorOnInvalidType = false) so the resolver returns null instead of throwing
  4. Check what the expression actually returns (SecurityContextHolder.getContext().getAuthentication().getClass()) and fix the mismatch

Example fix

// before
public Me me(@CurrentSecurityContext(expression = "authentication.principal") CustomUser user) { ... }
// after
public Me me(@CurrentSecurityContext(expression = "authentication.principal") Object principal) {
    CustomUser user = (principal instanceof CustomUser u) ? u : null;
    ...
}
Defensive patterns

Strategy: validation

Validate before calling

Object result = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (!parameterType.isAssignableFrom(result.getClass())) {
    // fix annotation expression or parameter type before deployment
}

Type guard

static <T> T safePrincipal(Class<T> type) {
    Object p = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    return type.isInstance(p) ? type.cast(p) : null;
}

Try / catch

try {
    return resolver.resolveArgument(param, null, request, null);
} catch (ClassCastException e) {
    log.warn("@CurrentSecurityContext type mismatch for {}: {}", param, e.getMessage());
    return null; // or map to 400
}

Prevention

When it happens

Trigger: A controller/handler method declares @CurrentSecurityContext SomeType param, the SpEL expression (default 'authentication' or a custom expression) evaluates to an object whose class is not assignable to SomeType (e.g. expression='authentication' but parameter type is CustomUserDetails, or expression returns Authentication but parameter is String).

Common situations: Declaring a parameter as a concrete UserDetails implementation while the principal is a different type; using a custom SpEL expression whose result type differs from the parameter type; copy-pasting resolvers across apps with different Authentication implementations; Spring Security 5.x where errorOnInvalidType defaults changed semantics for expression-based resolution.

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/4c89479c75a9e9ed. Report an issue: GitHub.