OpenFeign/feign · error · IllegalArgumentException

Method return type is not CompleteableFuture

Error message

Method return type is not CompleteableFuture: ${method}

What it means

Validation in TargetSpecificationVerifier.verify for async targets: a method's return type is a subtype/specialization of CompletableFuture (e.g. CompletionStage or a custom subclass) rather than exactly CompletableFuture. The synchronous case is skipped, but Feign requires async methods to return exactly CompletableFuture so it can complete them; other async types cannot be fulfilled by the client.

Solutions

  1. Change the method's return type to CompletableFuture<T> exactly
  2. Use CompletionStage only outside the Feign interface and convert from CompletableFuture in wrapper code
  3. Remove the method from the async interface if it should be synchronous
  4. Check generic wrappers/builders that may have rewritten the declared return type
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at core/src/main/java/feign/ReflectiveFeign.java:246 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of OpenFeign/feign@e2a1e27560 (2026-09-10). Data as JSON: /api/errors/b0356bed979756e0. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/feign/ReflectiveFeign.java:246

    }
  }

  private static class TargetSpecificationVerifier {
    public static <T> void verify(Target<T> target) {
      Class<T> type = target.type();
      if (!type.isInterface()) {
        throw new IllegalArgumentException("Type must be an interface: " + type);
      }

      for (final Method m : type.getMethods()) {
        final Class<?> retType = m.getReturnType();

        if (!CompletableFuture.class.isAssignableFrom(retType)) {
          continue; // synchronous case
        }

        if (retType != CompletableFuture.class) {
          throw new IllegalArgumentException(
              "Method return type is not CompleteableFuture: "
                  + getFullMethodName(type, retType, m));
        }

        final Type genRetType = m.getGenericReturnType();

        if (!(genRetType instanceof ParameterizedType)) {
          throw new IllegalArgumentException(
              "Method return type is not parameterized: " + getFullMethodName(type, genRetType, m));
        }

        if (((ParameterizedType) genRetType).getActualTypeArguments()[0] instanceof WildcardType) {
          throw new IllegalArgumentException(
              "Wildcards are not supported for return-type parameters: "
                  + getFullMethodName(type, genRetType, m));
        }
      }
    }

View on GitHub (pinned to e2a1e27560)