lysine-dev/retrofit · error · IllegalStateException
return type must be parameterized as or <? extends Foo>
Error message
${name} return type must be parameterized as ${name}<Foo> or ${name}<? extends Foo> What it means
Retrofit's RxJava 3.x adapter supports Observable, Flowable, Single, and Maybe (Completable is handled separately and needs no generic). Each must carry an element type; a raw type leaves the adapter unable to resolve the emitted item type, so RxJava3CallAdapterFactory throws IllegalStateException, interpolating the actual class name ('Flowable', 'Single', 'Maybe', or 'Observable') into the message.
Solutions
- Parameterize the return type: `Observable<List<User>> getUsers();`, `Single<User> findUser(@Path("id") long id);`, `Maybe<User> maybeUser(@Path("id") long id);`, or `Flowable<List<User>> users();`
- For no-payload calls use Completable: `Completable deleteUser(@Path("id") long id);`
- For full response/error packaging use `Observable<Response<User>>` or `Observable<Result<User>>`.
Example fix
// before Flowable users(); // after Flowable<List<User>> users();
Defensive patterns
Strategy: validation
Validate before calling
import java.lang.reflect.*;
import io.reactivex.rxjava3.core.*;
static void assertRx3TypesParameterized(Class<?> iface) {
for (Method m : iface.getDeclaredMethods()) {
Type rt = m.getGenericReturnType();
Class<?> raw = erasure(rt);
if ((raw==Observable.class||raw==Flowable.class||raw==Single.class||raw==Maybe.class)
&& !(rt instanceof ParameterizedType)) {
throw new AssertionError("Raw " + raw.getSimpleName() + " on " + m);
}
}
} Try / catch
try {
MyService svc = retrofit.create(MyService.class);
Single<User> s = svc.findUser(id);
} catch (IllegalStateException e) {
throw new IllegalStateException("RxJava3 return type is raw on the service interface: " + e.getMessage(), e);
} Prevention
- Build with -Xlint:rawtypes so raw reactive types fail the build.
- Use Completable for no-payload methods.
- Add a reflection unit test over the service interface.
When it happens
Trigger: A method with a raw return type such as `Flowable getUsers()`, `Single findUser()`, `Maybe maybeUser()`, or `Observable users()`. Thrown in get() when `returnType` is not a ParameterizedType; `name` is chosen from isFlowable/isSingle/isMaybe, defaulting to 'Observable'.
Common situations: Migrating from `Call<T>` to RxJava 3 and omitting the generic; mixing RxJava 2 and 3 types; suppress raw-types warnings.
Related errors
- return type must be parameterized as or <? extends Foo>
- return type must be parameterized as or <? extends Foo>
- CompletableFuture return type must be parameterized as…
- ListenableFuture return type must be parameterized as…
- Response must be parameterized as Response
AI-assisted analysis of lysine-dev/retrofit@d0b112dad0 (2026-08-13).
Data as JSON: /api/errors/fe7ad4a0e166559e.
Report an issue: GitHub.
Appendix: source
Thrown at retrofit-adapters/rxjava3/src/main/java/retrofit2/adapter/rxjava3/RxJava3CallAdapterFactory.java:123
// can only be created with a single configuration.
return new RxJava3CallAdapter(
Void.class, scheduler, isAsync, false, true, false, false, false, true);
}
boolean isFlowable = rawType == Flowable.class;
boolean isSingle = rawType == Single.class;
boolean isMaybe = rawType == Maybe.class;
if (rawType != Observable.class && !isFlowable && !isSingle && !isMaybe) {
return null;
}
boolean isResult = false;
boolean isBody = false;
Type responseType;
if (!(returnType instanceof ParameterizedType)) {
String name =
isFlowable ? "Flowable" : isSingle ? "Single" : isMaybe ? "Maybe" : "Observable";
throw new IllegalStateException(
name
+ " return type must be parameterized"
+ " as "
+ name
+ "<Foo> or "
+ name
+ "<? extends Foo>");
}
Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
Class<?> rawObservableType = getRawType(observableType);
if (rawObservableType == Response.class) {
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalStateException(
"Response must be parameterized" + " as Response<Foo> or Response<? extends Foo>");
}
responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
} else if (rawObservableType == Result.class) {View on GitHub (pinned to d0b112dad0)