{"record":{"id":"1adc81c9dc6a8603","repo":"spring-projects/spring-security","slug":"filter-target-must-be-a-collection-array-map-or","errorCode":null,"errorMessage":"Filter target must be a collection, array, map or stream type, but was {filterTarget}","messagePattern":"Filter target must be a collection, array, map or stream type, but was (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java","lineNumber":149,"sourceCode":"\t@Override\n\tpublic Object filter(@Nullable Object filterTarget, Expression filterExpression, EvaluationContext ctx) {\n\t\tMethodSecurityExpressionOperations rootObject = (MethodSecurityExpressionOperations) ctx.getRootObject()\n\t\t\t.getValue();\n\t\tAssert.notNull(rootObject, \"rootObject cannot be null\");\n\t\tthis.logger.debug(LogMessage.format(\"Filtering with expression: %s\", filterExpression.getExpressionString()));\n\t\tif (filterTarget instanceof Collection) {\n\t\t\treturn filterCollection((Collection<?>) filterTarget, filterExpression, ctx, rootObject);\n\t\t}\n\t\tif (filterTarget != null && filterTarget.getClass().isArray()) {\n\t\t\treturn filterArray((Object[]) filterTarget, filterExpression, ctx, rootObject);\n\t\t}\n\t\tif (filterTarget instanceof Map) {\n\t\t\treturn filterMap((Map<?, ?>) filterTarget, filterExpression, ctx, rootObject);\n\t\t}\n\t\tif (filterTarget instanceof Stream) {\n\t\t\treturn filterStream((Stream<?>) filterTarget, filterExpression, ctx, rootObject);\n\t\t}\n\t\tthrow new IllegalArgumentException(\n\t\t\t\t\"Filter target must be a collection, array, map or stream type, but was \" + filterTarget);\n\t}\n\n\tprivate <T> Object filterCollection(Collection<T> filterTarget, Expression filterExpression, EvaluationContext ctx,\n\t\t\tMethodSecurityExpressionOperations rootObject) {\n\t\tthis.logger.debug(LogMessage.format(\"Filtering collection with %s elements\", filterTarget.size()));\n\t\tList<T> retain = new ArrayList<>(filterTarget.size());\n\t\tif (this.permissionCacheOptimizer != null) {\n\t\t\tthis.permissionCacheOptimizer.cachePermissionsFor(rootObject.getAuthentication(), filterTarget);\n\t\t}\n\t\tfor (T filterObject : filterTarget) {\n\t\t\trootObject.setFilterObject(filterObject);\n\t\t\tif (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {\n\t\t\t\tretain.add(filterObject);\n\t\t\t}\n\t\t}\n\t\tthis.logger.debug(LogMessage.format(\"Retaining elements: %s\", retain));\n\t\ttry {","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java#L131-L167","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n@PostFilter(\"hasPermission(filterObject, 'READ')\")\nOptional<Document> findDocument();\n// after\n@PostFilter(\"hasPermission(filterObject, 'READ')\")\nList<Document> findDocuments();","handlingStrategy":"type-guard","validationCode":"if (!(result instanceof Collection || result instanceof Map || result instanceof Stream || result instanceof Object[]))\n  throw new IllegalArgumentException(\"@PostFilter target must be collection/array/map/stream\");","typeGuard":"boolean filterable(Object o) { return o instanceof Collection || o instanceof Map || o instanceof Stream || (o != null && o.getClass().isArray()); }","tryCatchPattern":"try { return filteredMethod(); }\ncatch (IllegalArgumentException e) { log.error(\"Unsupported @PostFilter target\", e); return Collections.emptyList(); }","preventionTips":["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"],"tags":["spel","method-security","postfilter","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"}