OpenFeign/feign · error · IllegalArgumentException
Method of contract doesn't returns a…
Error message
Method %s of contract %s doesn't returns a org.reactivestreams.Publisher
What it means
ReactiveDelegatingContract validates that every method of the target interface returns a type implementing org.reactivestreams.Publisher (e.g. Flux, Mono, Observable). If any method's declared return type is not reactive, the contract cannot wrap it, so parseAndValidateMetadata throws IllegalArgumentException. This is a fail-fast check during Feign target creation.
Solutions
- Change every method in the target interface to return a Publisher subtype (reactor Flux/Mono or rx.Observable depending on module)
- If some methods must stay synchronous, split them into a separate interface and build a second, non-reactive Feign target
- Verify you are using the correct builder: reactor module expects Flux/Mono, rxjava module expects Observable/Single
Example fix
// before
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}")
String repo(String owner, String repo);
}
// after
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}")
Mono<String> repo(String owner, String repo); // or Flux<T> for collections
} Defensive patterns
Strategy: validation
Validate before calling
for (java.lang.reflect.Method m : Api.class.getMethods()) {
if (!org.reactivestreams.Publisher.class.isAssignableFrom(m.getReturnType()))
throw new IllegalStateException(m + " must return a Publisher (Flux/Mono)");
} Type guard
static boolean isReactiveReturn(java.lang.reflect.Method m) {
return org.reactivestreams.Publisher.class.isAssignableFrom(m.getReturnType());
} Prevention
- Define every method of reactive Feign interfaces with Flux/Mono/Observable return types from the start
- Keep synchronous interfaces separate from reactive ones
- Write a unit test that builds the reactive target early (fails fast at startup)
When it happens
Trigger: Declaring a Feign interface method with a plain return type (String, Response, List<T>, CompletableFuture, etc.) and passing the interface to ReactorFeign/RxJavaFeign via a ReactiveDelegatingContract-based builder.
Common situations: Reusing an existing synchronous Feign interface with a reactive builder; migrating code from classic Feign.builder() to the reactive module without changing return types; copy-pasting method signatures from non-reactive clients.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Expected only one contained type.
- Return type is not supported
- Streams are not supported when using Reactive Wrappers
- Streaming Decoding is not supported.
- Invocation Handler Factory overrides are not supported.
AI-assisted analysis of OpenFeign/feign@e2a1e27560 (2026-09-10).
Data as JSON: /api/errors/fec9c904d9f92380.
Report an issue: GitHub.
Appendix: source
Thrown at reactive/src/main/java/feign/reactive/ReactiveDelegatingContract.java:42
import java.util.stream.Stream;
import org.reactivestreams.Publisher;
public class ReactiveDelegatingContract implements Contract {
private final Contract delegate;
ReactiveDelegatingContract(Contract delegate) {
this.delegate = delegate;
}
@Override
public List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType) {
List<MethodMetadata> methodsMetadata = this.delegate.parseAndValidateMetadata(targetType);
for (final MethodMetadata metadata : methodsMetadata) {
final Type type = metadata.returnType();
if (!isReactive(type)) {
throw new IllegalArgumentException(
String.format(
"Method %s of contract %s doesn't returns a org.reactivestreams.Publisher",
metadata.configKey(), targetType.getSimpleName()));
}
/*
* we will need to change the return type of the method to match the return type contained
* within the Publisher
*/
Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
if (actualTypes.length > 1) {
throw new IllegalStateException("Expected only one contained type.");
} else {
Class<?> actual = Types.getRawType(actualTypes[0]);
if (Stream.class.isAssignableFrom(actual)) {
throw new IllegalArgumentException(
"Streams are not supported when using Reactive Wrappers");
}View on GitHub (pinned to e2a1e27560)