OpenFeign/feign · error · IllegalStateException

Method of contract doesn't returns io.vertx.core.Future

Error message

Method %s of contract %s doesn't returns io.vertx.core.Future

What it means

VertxDelegatingContract.parseAndValidateMetadata validates at interface-parse time that every method's declared return type is a ParameterizedType whose raw type is io.vertx.core.Future. The Vert.x contract only knows how to substitute the Future's actual type argument as the Feign return type; anything else is rejected with an IllegalStateException naming the method's configKey and target type.

Solutions

  1. Make the return type io.vertx.core.Future<ActualType> with an explicit type argument.
  2. Remove or split out methods that do not return Future into a separate synchronous interface.
  3. Verify the raw type is exactly io.vertx.core.Future, not a custom Future subtype.
  4. If using reactive wrappers, use the matching integration (reactive streams modules) instead of the Vert.x contract.

Example fix

// before
@RequestLine("GET /items")
Future listItems();
// after
@RequestLine("GET /items")
Future<List<Item>> listItems();
Defensive patterns

Strategy: validation

Validate before calling

for (Method m : Api.class.getDeclaredMethods()) {
  if (!(m.getGenericReturnType() instanceof ParameterizedType)
      || ((ParameterizedType) m.getGenericReturnType()).getRawType() != io.vertx.core.Future.class) {
    throw new IllegalStateException(m + " must return io.vertx.core.Future<T>");
  }
}

Type guard

static boolean isParameterizedFuture(Type t) {
  return t instanceof ParameterizedType
      && ((ParameterizedType) t).getRawType() == io.vertx.core.Future.class;
}

Try / catch

try {
  VertxVertx.feign().target(Api.class, baseUrl);
} catch (IllegalStateException e) {
  // contract parse failure: a method lacks a parameterized Future return type
}

Prevention

When it happens

Trigger: Declaring a method on a contract parsed by VertxDelegatingContract whose return type is a raw Future (no type parameter), a Future subclass, or a non-Future type such as User, List<User>, CompletableFuture<User>, or void.

Common situations: Forgetting the generic parameter (raw Future instead of Future<User>); returning CompletableFuture/Single/Mono from a reactor/reactivex-style contract; a shared API interface reused across sync and Vert.x clients.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at vertx/feign-vertx/src/main/java/feign/vertx/VertxDelegatingContract.java:54

  public VertxDelegatingContract(final Contract delegate) {
    this.delegate = checkNotNull(delegate, "delegate must not be null");
  }

  @Override
  public List<MethodMetadata> parseAndValidateMetadata(final Class<?> targetType) {
    checkNotNull(targetType, "Argument targetType must be not null");

    final List<MethodMetadata> metadatas = delegate.parseAndValidateMetadata(targetType);

    for (final MethodMetadata metadata : metadatas) {
      final Type type = metadata.returnType();

      if (type instanceof ParameterizedType
          && ((ParameterizedType) type).getRawType().equals(Future.class)) {
        final Type actualType = resolveLastTypeParameter(type, Future.class);
        metadata.returnType(actualType);
      } else {
        throw new IllegalStateException(
            String.format(
                "Method %s of contract %s doesn't returns io.vertx.core.Future",
                metadata.configKey(), targetType.getSimpleName()));
      }
    }

    return metadatas;
  }
}

View on GitHub (pinned to e2a1e27560)