{"record":{"id":"d6e8837f9ecc1e2f","repo":"Netflix/Hystrix","slug":"batch-method-must-be-annotated-with-hystrixcommand","errorCode":null,"errorMessage":"batch method must be annotated with HystrixCommand annotation","messagePattern":"batch method must be annotated with HystrixCommand annotation","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":234,"sourceCode":"                        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());\n            if (!collapserMethodReturnType\n                    .equals(batchCommandActualReturnType)) {\n                throw new IllegalStateException(\"Return type of batch method must be java.util.List parametrized with corresponding type: expected \" +\n                        \"(java.util.List<\" + collapserMethodReturnType + \">)\" + obj.getClass().getCanonicalName() + \".\" +\n                        hystrixCollapser.batchMethod() + \"(java.util.List<\" + collapserMethod.getParameterTypes()[0] + \">), but it's \" +\n                        batchCommandActualReturnType);\n            }\n\n            HystrixCommand hystrixCommand = batchCommandMethod.getAnnotation(HystrixCommand.class);\n            if (hystrixCommand == null) {\n                throw new IllegalStateException(\"batch method must be annotated with HystrixCommand annotation\");\n            }\n            // method of batch hystrix command must be passed to metaholder because basically collapser doesn't have any actions\n            // that should be invoked upon intercepted method, it's required only for underlying batch command\n\n            MetaHolder.Builder builder = metaHolderBuilder(proxy, batchCommandMethod, obj, args, joinPoint);\n\n            if (isCompileWeaving()) {\n                builder.ajcMethod(getAjcMethodAroundAdvice(obj.getClass(), batchCommandMethod.getName(), List.class));\n            }\n\n            builder.hystrixCollapser(hystrixCollapser);\n            builder.defaultCollapserKey(collapserMethod.getName());\n            builder.collapserExecutionType(ExecutionType.getExecutionType(collapserReturnType));\n\n            builder.defaultCommandKey(batchCommandMethod.getName());\n            builder.hystrixCommand(hystrixCommand);\n            builder.executionType(ExecutionType.getExecutionType(batchReturnType));\n            builder.observable(observable);","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/Netflix/Hystrix/blob/5ce3bc58c38e7ca60ef2fe0e516e390e294ad941/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java#L216-L252","documentation":"The batch method referenced by @HystrixCollapser(batchMethod = ...) must itself be annotated with @HystrixCommand, because the batch execution is what actually runs as a Hystrix command (the collapser only batches requests). Javanica reads @HystrixCommand off the batch method to build the underlying command's properties, fallback, etc., and throws IllegalStateException when it is missing.","triggerScenarios":"batchMethod points at a public method that has no @HystrixCommand annotation (only the collapser method is annotated); the annotation was removed during refactoring; the annotation imported is from a different package (e.g. a custom or old com.netflix.hystrix.javanica 3.x artifact).","commonSituations":"Annotating just the collapser assuming it is enough; IDE auto-import resolving HystrixCommand from an unexpected package; annotation stripped when moving the batch method into another class.","solutions":["Add @HystrixCommand to the batch method.","Verify the import is com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand.","Keep collapser and batch method in the same class and review both when refactoring."],"exampleFix":"// before\n@HystrixCollapser(batchMethod = \"getUserByIds\")\npublic Future<User> getUserById(String id) { ... }\npublic List<User> getUserByIds(List<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 assertBatchCommandAnnotated(Class<?> clazz, HystrixCollapser c) {\n    for (Method m : clazz.getDeclaredMethods()) {\n        if (m.getName().equals(c.batchMethod()) && m.getParameterTypes().length == 1\n                && m.getParameterTypes()[0] == java.util.List.class) {\n            if (!m.isAnnotationPresent(HystrixCommand.class))\n                throw new IllegalStateException(m + \" must be annotated with @HystrixCommand\");\n            return;\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"Catch IllegalStateException at first collapser invocation in integration tests; 'batch method must be annotated' means the wiring is incomplete — fail fast, never catch-and-continue in production paths.","preventionTips":["Copy the canonical collapser/batch pair from the javanica docs whenever adding one — batch method always carries @HystrixCommand.","Add a startup/CI reflection test validating the annotation on every batch method."],"tags":["hystrix","javanica","collapser","annotation","misuse"],"backgroundTag":null,"analyzedSha":"5ce3bc58c38e7ca60ef2fe0e516e390e294ad941","analyzedAt":"2026-08-14T10:55:35.600Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}