Netflix/Hystrix · error · IllegalStateException

required batch method for collapser is absent, wrong generic

Error message

required batch method for collapser is absent, wrong generic type: expected {}.{}(java.util.List<{}>), but it's {}

What it means

The batch method's single parameter must be a parameterized List whose element type equals the collapser method's parameter type. Javanica extracts the first actual type argument of the batch method's generic parameter (e.g. String from List<String>) and compares it to the collapser's parameter class; a mismatch throws IllegalStateException with a message spelling out the expected signature.

Source

Thrown at hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java:213

        @Override
        public MetaHolder create(Object proxy, Method collapserMethod, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) {
            HystrixCollapser hystrixCollapser = collapserMethod.getAnnotation(HystrixCollapser.class);
            if (collapserMethod.getParameterTypes().length > 1 || collapserMethod.getParameterTypes().length == 0) {
                throw new IllegalStateException("Collapser method must have one argument: " + collapserMethod);
            }

            Method batchCommandMethod = getDeclaredMethod(obj.getClass(), hystrixCollapser.batchMethod(), List.class);

            if (batchCommandMethod == null)
                throw new IllegalStateException("batch method is absent: " + hystrixCollapser.batchMethod());

            Class<?> batchReturnType = batchCommandMethod.getReturnType();
            Class<?> collapserReturnType = collapserMethod.getReturnType();
            boolean observable = collapserReturnType.equals(Observable.class);

            if (!collapserMethod.getParameterTypes()[0]
                    .equals(getFirstGenericParameter(batchCommandMethod.getGenericParameterTypes()[0]))) {
                throw new IllegalStateException("required batch method for collapser is absent, wrong generic type: expected "
                        + obj.getClass().getCanonicalName() + "." +
                        hystrixCollapser.batchMethod() + "(java.util.List<" + collapserMethod.getParameterTypes()[0] + ">), but it's " +
                        getFirstGenericParameter(batchCommandMethod.getGenericParameterTypes()[0]));
            }

            final Class<?> collapserMethodReturnType = getFirstGenericParameter(
                    collapserMethod.getGenericReturnType(),
                    Future.class.isAssignableFrom(collapserReturnType) || Observable.class.isAssignableFrom(collapserReturnType) ? 1 : 0);

            Class<?> batchCommandActualReturnType = getFirstGenericParameter(batchCommandMethod.getGenericReturnType());
            if (!collapserMethodReturnType
                    .equals(batchCommandActualReturnType)) {
                throw new IllegalStateException("Return type of batch method must be java.util.List parametrized with corresponding type: expected " +
                        "(java.util.List<" + collapserMethodReturnType + ">)" + obj.getClass().getCanonicalName() + "." +
                        hystrixCollapser.batchMethod() + "(java.util.List<" + collapserMethod.getParameterTypes()[0] + ">), but it's " +
                        batchCommandActualReturnType);
            }

View on GitHub (pinned to 5ce3bc58c3)

Solutions

  1. Make the batch method's List element type exactly equal to the collapser method's parameter type.
  2. If the type was changed recently, update both signatures in the same commit.
  3. Avoid raw or wildcard-generic List parameters on the batch method.

Example fix

// before
@HystrixCollapser(batchMethod = "getUserByIds")
public Future<User> getUserById(Long id) { ... }
@HystrixCommand
public List<User> getUserByIds(List<String> ids) { ... }  // mismatch

// after
@HystrixCommand
public List<User> getUserByIds(List<Long> ids) { ... }
Defensive patterns

Strategy: validation

Validate before calling

static void assertBatchElementType(Class<?> clazz, Method collapser, Method batch) {
    Class<?> expected = collapser.getParameterTypes()[0];
    Type p = batch.getGenericParameterTypes()[0];
    if (!(p instanceof java.lang.reflect.ParameterizedType))
        throw new IllegalStateException(batch + " parameter must be a parameterized List");
    Class<?> actual = (Class<?>) ((java.lang.reflect.ParameterizedType) p).getActualTypeArguments()[0];
    if (!expected.equals(actual))
        throw new IllegalStateException("batch List element " + actual + " != collapser param " + expected);
}

Try / catch

Fail fast in tests: catch IllegalStateException around the first collapser call and assert the message does not contain 'wrong generic type'; report as a compile-time-style contract violation.

Prevention

When it happens

Trigger: Collapser takes Integer but the batch method is batch(List<String>); or the batch parameter is a raw List (no generic argument, which instead trips the 'expected to be generic' error); or the element type is a subclass rather than the exact same class (the check uses Class.equals, not isAssignableFrom).

Common situations: Changing the collapser parameter type (Long ids instead of String) without updating the batch method; using different wrapper types (int vs Integer); batch method declared with a supertype element like List<Object>.

Related errors


AI-assisted analysis of Netflix/Hystrix@5ce3bc58c3 (2026-08-14). Data as JSON: /api/errors/cf072e3877880958. Report an issue: GitHub.