spring-projects/spring-security · error · IllegalArgumentException
Filter target must be a collection, array, map or stream typ
Error message
Filter target must be a collection, array, map or stream type, but was {filterTarget} What it means
DefaultMethodSecurityExpressionHandler.filter applies a @PostFilter expression but only supports Collection, array, Map, and Stream targets. Any other object passed as the filter target (the return value or filterTarget parameter) is rejected with IllegalArgumentException naming the offending value's type.
Source
Thrown at core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java:149
@Override
public Object filter(@Nullable Object filterTarget, Expression filterExpression, EvaluationContext ctx) {
MethodSecurityExpressionOperations rootObject = (MethodSecurityExpressionOperations) ctx.getRootObject()
.getValue();
Assert.notNull(rootObject, "rootObject cannot be null");
this.logger.debug(LogMessage.format("Filtering with expression: %s", filterExpression.getExpressionString()));
if (filterTarget instanceof Collection) {
return filterCollection((Collection<?>) filterTarget, filterExpression, ctx, rootObject);
}
if (filterTarget != null && filterTarget.getClass().isArray()) {
return filterArray((Object[]) filterTarget, filterExpression, ctx, rootObject);
}
if (filterTarget instanceof Map) {
return filterMap((Map<?, ?>) filterTarget, filterExpression, ctx, rootObject);
}
if (filterTarget instanceof Stream) {
return filterStream((Stream<?>) filterTarget, filterExpression, ctx, rootObject);
}
throw new IllegalArgumentException(
"Filter target must be a collection, array, map or stream type, but was " + filterTarget);
}
private <T> Object filterCollection(Collection<T> filterTarget, Expression filterExpression, EvaluationContext ctx,
MethodSecurityExpressionOperations rootObject) {
this.logger.debug(LogMessage.format("Filtering collection with %s elements", filterTarget.size()));
List<T> retain = new ArrayList<>(filterTarget.size());
if (this.permissionCacheOptimizer != null) {
this.permissionCacheOptimizer.cachePermissionsFor(rootObject.getAuthentication(), filterTarget);
}
for (T filterObject : filterTarget) {
rootObject.setFilterObject(filterObject);
if (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {
retain.add(filterObject);
}
}
this.logger.debug(LogMessage.format("Retaining elements: %s", retain));
try {View on GitHub (pinned to 96852e8860)
Solutions
- Change the method to return a supported type (List/Set/Map/Stream/array) and apply @PostFilter to it
- Unwrap Optional or Page into a collection (e.g. return page.getContent()) before filtering
- Remove @PostFilter and filter manually in code using the security expression logic
- If a custom wrapper is needed, make it implement Collection so the handler can filter it
Example fix
// before
@PostFilter("hasPermission(filterObject, 'READ')")
Optional<Document> findDocument();
// after
@PostFilter("hasPermission(filterObject, 'READ')")
List<Document> findDocuments(); Defensive patterns
Strategy: type-guard
Validate before calling
if (!(result instanceof Collection || result instanceof Map || result instanceof Stream || result instanceof Object[]))
throw new IllegalArgumentException("@PostFilter target must be collection/array/map/stream"); Type guard
boolean filterable(Object o) { return o instanceof Collection || o instanceof Map || o instanceof Stream || (o != null && o.getClass().isArray()); } Try / catch
try { return filteredMethod(); }
catch (IllegalArgumentException e) { log.error("Unsupported @PostFilter target", e); return Collections.emptyList(); } Prevention
- Never put @PostFilter on methods returning Optional, Page, or single entities
- Return List/Stream from repository methods intended for filtering
- Unwrap Page via getContent() in the service layer
- Add architecture tests asserting @PostFilter methods return filterable types
When it happens
Trigger: Using @PostFilter on a method whose return type is not a Collection/array/Map/Stream (e.g. a single object, Optional, String, custom type), or calling filter() directly with such an object as filterTarget.
Common situations: Adding @PostFilter to repository methods returning Optional<T> or Page<T> without unwrapping; expecting @PostFilter to filter a single object; filtering custom collection wrappers not extending Collection.
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
- Access is denied
- org.springframework.security.config.annotation.method.config
- Did you forget to add a global <authentication-manager> elem
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/1adc81c9dc6a8603.
Report an issue: GitHub.