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 1.x adapter supports Observable, Single, and Completable return types. Observable and Single must carry a generic element type; a raw type leaves the adapter unable to resolve the emitted item type, so RxJavaCallAdapterFactory throws IllegalStateException, interpolating the actual raw class name ('Single' or 'Observable') into the message. Completable is exempt because it has no type parameter.

Source

Thrown at retrofit-adapters/rxjava/src/main/java/retrofit2/adapter/rxjava/RxJavaCallAdapterFactory.java:114

  public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    Class<?> rawType = getRawType(returnType);
    boolean isSingle = rawType == Single.class;
    boolean isCompletable = rawType == Completable.class;
    if (rawType != Observable.class && !isSingle && !isCompletable) {
      return null;
    }

    if (isCompletable) {
      return new RxJavaCallAdapter(Void.class, scheduler, isAsync, false, true, false, true);
    }

    boolean isResult = false;
    boolean isBody = false;
    Type responseType;
    if (!(returnType instanceof ParameterizedType)) {
      String name = isSingle ? "Single" : "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

  1. Parameterize the return type: `Observable<List<User>> getUsers();` or `Single<User> findUser(@Path("id") long id);`
  2. For fire-and-forget calls with no payload use Completable: `Completable deleteUser(@Path("id") long id);`
  3. For full response or error packaging use `Observable<Response<User>>` or `Observable<Result<User>>`.

Example fix

// before
Observable getUsers();
// after
Observable<List<User>> getUsers();
Defensive patterns

Strategy: validation

Validate before calling

import java.lang.reflect.*;
import rx.*;
static void assertRxTypesParameterized(Class<?> iface) {
  for (Method m : iface.getDeclaredMethods()) {
    Type rt = m.getGenericReturnType();
    Class<?> raw = rt instanceof Class ? (Class<?>) rt : getRaw(rt);
    if ((raw == Observable.class || raw == Single.class) && !(rt instanceof ParameterizedType)) {
      throw new AssertionError("Raw " + raw.getSimpleName() + " return type on " + m);
    }
  }
}

Try / catch

try {
  MyService svc = retrofit.create(MyService.class);
  Observable<List<User>> o = svc.getUsers();
} catch (IllegalStateException e) {
  throw new IllegalStateException("RxJava return type is raw on the service interface: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: A method with a raw return type such as `Observable getUsers()` or `Single findUser()`. Thrown in get() when `returnType` is not a ParameterizedType and the type is not Completable; the `name` variable is set to 'Single' when isSingle else 'Observable'.

Common situations: Migrating from `Call<T>` to RxJava and omitting the generic; mixing RxJava 1 and 2 adapter types; suppress raw-types warnings.

Related errors


AI-assisted analysis of lysine-dev/retrofit@d0b112dad0 (2026-08-13). Data as JSON: /api/errors/13611b65a6eeaf02. Report an issue: GitHub.