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

  1. 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();`
  2. For no-payload calls use Completable: `Completable deleteUser(@Path("id") long id);`
  3. 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

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


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