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

  1. Change the method to return a supported type (List/Set/Map/Stream/array) and apply @PostFilter to it
  2. Unwrap Optional or Page into a collection (e.g. return page.getContent()) before filtering
  3. Remove @PostFilter and filter manually in code using the security expression logic
  4. 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

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


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/1adc81c9dc6a8603. Report an issue: GitHub.