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
- Make the return type io.vertx.core.Future<ActualType> with an explicit type argument.
- Remove or split out methods that do not return Future into a separate synchronous interface.
- Verify the raw type is exactly io.vertx.core.Future, not a custom Future subtype.
- 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
- Never use raw Future as a return type; always supply the type parameter
- Keep non-Future methods in a separate interface used only with the default contract
- Create the proxy in application startup code so invalid contracts fail fast
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
- Method return type is not parameterized
- Wildcards are not supported for return-type parameters
- formatted errorMessageTemplate with errorMessageArgs
- an expression is required.
- variable map is required.
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)