{"record":{"id":"23a0a67642a2b622","repo":"Netflix/Hystrix","slug":"batch-method-is-absent","errorCode":null,"errorMessage":"batch method is absent: {}","messagePattern":"batch method is absent: (.+?)","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":205,"sourceCode":"            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            }\n\n            final Class<?> collapserMethodReturnType = getFirstGenericParameter(\n                    collapserMethod.getGenericReturnType(),\n                    Future.class.isAssignableFrom(collapserReturnType) || Observable.class.isAssignableFrom(collapserReturnType) ? 1 : 0);\n\n            Class<?> batchCommandActualReturnType = getFirstGenericParameter(batchCommandMethod.getGenericReturnType());","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/Netflix/Hystrix/blob/5ce3bc58c38e7ca60ef2fe0e516e390e294ad941/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java#L187-L223","documentation":"The @HystrixCollapser annotation names a batchMethod, and Javanica looks it up on the same class via getDeclaredMethod(clazz, batchMethod, List.class). If no method with that exact name and a single java.util.List parameter exists (declared on the class, not inherited), an IllegalStateException is thrown. Note it uses getDeclaredMethod, so the batch method must be declared directly on the target class.","triggerScenarios":"@HystrixCollapser(batchMethod = \"foo\") where foo does not exist, is misspelled, has a different name, or exists but does not take exactly one List parameter (e.g. takes Collection, varargs, or List plus another arg). Also thrown when the batch method is inherited from a superclass rather than declared on the class.","commonSituations":"Renaming the batch method without updating batchMethod; batch method defined in an interface or superclass; batch method parameter typed as Collection<String> or ArrayList instead of List.","solutions":["Declare a method with the exact batchMethod name on the same class taking exactly one java.util.List parameter.","Check spelling/case of the batchMethod attribute against the real method name.","If the batch method lives in a superclass, move or override it on the collapser's class (lookup uses getDeclaredMethod)."],"exampleFix":"// before\n@HystrixCollapser(batchMethod = \"getUserByIds\")\npublic Future<User> getUserById(String id) { ... }\n// no matching method, or: public List<User> getUserByIds(Collection<String> ids)\n\n// after\n@HystrixCollapser(batchMethod = \"getUserByIds\")\npublic Future<User> getUserById(String id) { ... }\n\n@HystrixCommand\npublic List<User> getUserByIds(List<String> ids) { ... }","handlingStrategy":"validation","validationCode":"static void assertBatchMethodExists(Class<?> clazz, HystrixCollapser c) {\n    boolean found = false;\n    for (Method m : clazz.getDeclaredMethods()) {\n        if (m.getName().equals(c.batchMethod())\n                && m.getParameterTypes().length == 1\n                && m.getParameterTypes()[0] == java.util.List.class) { found = true; break; }\n    }\n    if (!found) throw new IllegalStateException(\"batch method missing: \" + clazz.getName() + \".\" + c.batchMethod() + \"(List)\");\n}","typeGuard":null,"tryCatchPattern":"Startup self-test: invoke each collapser once in a test context inside try/catch (IllegalStateException) and surface the message; treat any 'batch method is absent' as build failure.","preventionTips":["Keep collapser method and batch method adjacent in the same class and update batchMethod strings via IDE rename refactoring (it does not rename string references — so prefer constants or tests).","Reflection-based CI test enumerating all @HystrixCollapser annotations and verifying the named batch method exists with a single List parameter on the declaring class."],"tags":["hystrix","javanica","collapser","batch-method","misuse"],"backgroundTag":null,"analyzedSha":"5ce3bc58c38e7ca60ef2fe0e516e390e294ad941","analyzedAt":"2026-08-14T10:55:35.600Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}