lysine-dev/retrofit · error · IllegalStateException
${name} return type must be parameterized as ${name}<Foo> or
Error message
${name} return type must be parameterized as ${name}<Foo> or ${name}<? extends Foo> What it means
Retrofit's RxJava 2.x adapter supports Observable, Flowable, Single, and Maybe (Completable is handled separately and needs no generic). Each of those must carry an element type; a raw type leaves the adapter unable to resolve what each emitted item is, so RxJava2CallAdapterFactory throws IllegalStateException, interpolating the actual class name ('Flowable', 'Single', 'Maybe', or 'Observable') into the message.
Source
Thrown at retrofit-adapters/rxjava2/src/main/java/retrofit2/adapter/rxjava2/RxJava2CallAdapterFactory.java:118
// can only be created with a single configuration.
return new RxJava2CallAdapter(
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)
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
Single findUser(@Path("id") long id);
// after
Single<User> findUser(@Path("id") long id); Defensive patterns
Strategy: validation
Validate before calling
import java.lang.reflect.*;
import io.reactivex.*;
static void assertRx2TypesParameterized(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("RxJava2 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 2 and omitting the generic; mixing RxJava 1 and 2 types; suppress raw-types warnings.
Related errors
- ${name} return type must be parameterized as ${name}<Foo> or
- Response must be parameterized as Response<Foo> or Response<
- Result must be parameterized as Result<Foo> or Result<? exte
- ${name} return type must be parameterized as ${name}<Foo> or
- ListenableFuture return type must be parameterized as Listen
AI-assisted analysis of lysine-dev/retrofit@d0b112dad0 (2026-08-13).
Data as JSON: /api/errors/da5bd26c217267ef.
Report an issue: GitHub.