{"record":{"id":"b79e53811c213ed1","repo":"Netflix/Hystrix","slug":"collapser-method-must-have-one-argument","errorCode":null,"errorMessage":"Collapser method must have one argument: {}","messagePattern":"Collapser method must have one argument: (.+?)","errorType":"validation","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java","lineNumber":199,"sourceCode":"\n        MetaHolder.Builder metaHolderBuilder(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) {\n            MetaHolder.Builder builder = MetaHolder.builder()\n                    .args(args).method(method).obj(obj).proxyObj(proxy)\n                    .joinPoint(joinPoint);\n\n            setFallbackMethod(builder, obj.getClass(), method);\n            builder = setDefaultProperties(builder, obj.getClass(), joinPoint);\n            return builder;\n        }\n    }\n\n    private static class CollapserMetaHolderFactory extends MetaHolderFactory {\n\n        @Override\n        public MetaHolder create(Object proxy, Method collapserMethod, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) {\n            HystrixCollapser hystrixCollapser = collapserMethod.getAnnotation(HystrixCollapser.class);\n            if (collapserMethod.getParameterTypes().length > 1 || collapserMethod.getParameterTypes().length == 0) {\n                throw new IllegalStateException(\"Collapser method must have one argument: \" + collapserMethod);\n            }\n\n            Method batchCommandMethod = getDeclaredMethod(obj.getClass(), hystrixCollapser.batchMethod(), List.class);\n\n            if (batchCommandMethod == null)\n                throw new IllegalStateException(\"batch method is absent: \" + hystrixCollapser.batchMethod());\n\n            Class<?> batchReturnType = batchCommandMethod.getReturnType();\n            Class<?> collapserReturnType = collapserMethod.getReturnType();\n            boolean observable = collapserReturnType.equals(Observable.class);\n\n            if (!collapserMethod.getParameterTypes()[0]\n                    .equals(getFirstGenericParameter(batchCommandMethod.getGenericParameterTypes()[0]))) {\n                throw new IllegalStateException(\"required batch method for collapser is absent, wrong generic type: expected \"\n                        + obj.getClass().getCanonicalName() + \".\" +\n                        hystrixCollapser.batchMethod() + \"(java.util.List<\" + collapserMethod.getParameterTypes()[0] + \">), but it's \" +\n                        getFirstGenericParameter(batchCommandMethod.getGenericParameterTypes()[0]));\n            }","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/Netflix/Hystrix/blob/5ce3bc58c38e7ca60ef2fe0e516e390e294ad941/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java#L181-L217","documentation":"A @HystrixCollapser method must take exactly one argument. The collapser's contract is that each call contributes its single argument as one element of the List that is eventually passed to the batch method, so zero or multiple parameters make request collapsing undefined and CollapserMetaHolderFactory throws IllegalStateException during meta-holder creation.","triggerScenarios":"Calling a @HystrixCollapser-annotated method whose signature has 0 parameters or more than 1 parameter (e.g. getUserById(String id, String tenant)). The check fires before the batch method is even looked up.","commonSituations":"Adding a second parameter (timeout, tenant id, locale) to an existing collapser method later; writing a collapser around a no-arg lookup method.","solutions":["Reduce the collapser method to exactly one parameter and fold extra context into that parameter (a small value object) or into the command's groupKey/properties.","If you need multiple independent arguments, use a plain @HystrixCommand instead of a collapser.","Verify the batch method's List element type still matches the (new) single parameter type."],"exampleFix":"// before\n@HystrixCollapser(batchMethod = \"getUserByIds\")\npublic Future<User> getUserById(String id, String tenant) { ... }\n\n// after\n@HystrixCollapser(batchMethod = \"getUserByIds\")\npublic Future<User> getUserById(UserRequest req) { ... }  // UserRequest carries id + tenant","handlingStrategy":"validation","validationCode":"static void assertCollapserSignature(Method m) {\n    if (!m.isAnnotationPresent(HystrixCollapser.class)) return;\n    int n = m.getParameterTypes().length;\n    if (n != 1) throw new IllegalArgumentException(m + \" must have exactly one parameter, has \" + n);\n}","typeGuard":null,"tryCatchPattern":"Wrap first collapser invocation in a smoke test / startup probe with try { ... } catch (IllegalStateException e) { fail fast with the collapser method name in the message }; configuration errors should crash tests, not production calls.","preventionTips":["Add a reflection-based unit test that validates every @HystrixCollapser method in the app has exactly one parameter.","When a collapser method needs more context, introduce a single parameter object instead of adding parameters."],"tags":["hystrix","javanica","collapser","signature","misuse"],"backgroundTag":null,"analyzedSha":"5ce3bc58c38e7ca60ef2fe0e516e390e294ad941","analyzedAt":"2026-08-14T10:55:35.600Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}