square/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

Thrown by RxJava2CallAdapterFactory while validating a service method's return type. `${name}` resolves to one of `Flowable`, `Single`, `Maybe`, or `Observable` (chosen at RxJava2CallAdapterFactory.java:117). A raw reactive type gives no body type, so no converter can be selected; the check at line 115 (`returnType instanceof ParameterizedType`) rejects it. Completable is handled separately and does not require a type parameter.

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 reactive type: `Observable<User> getUser();` (or Flowable/Single/Maybe).
  2. Wrap the body for HTTP metadata: `Observable<Response<User>>` or `Observable<Result<User>>`.
  3. Use `Completable` for no-body endpoints (no parameterization needed).

Example fix

// before
@GET("users/{id}")
Observable getUser(@Path("id") String id);

// after
@GET("users/{id}")
Observable<User> getUser(@Path("id") String id);
Defensive patterns

Strategy: validation

Validate before calling

// javac -Xlint:rawtypes -Werror
for (Method m : MyService.class.getDeclaredMethods()) {
    Class<?> raw = m.getReturnType();
    if (raw == io.reactivex.Observable.class
            || raw == io.reactivex.Flowable.class
            || raw == io.reactivex.Single.class
            || raw == io.reactivex.Maybe.class) {
        throw new IllegalStateException(m + " returns raw " + raw.getSimpleName() + "; parameterize it.");
    }
}

Try / catch

try {
    retrofit.create(MyService.class).getUser("1");
} catch (IllegalStateException e) {
    if (e.getMessage().contains("return type must be parameterized")) {
        throw new IllegalStateException("Raw RxJava2 type in MyService; parameterize it.", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: A service method returns a raw `Observable`, `Flowable`, `Single`, or `Maybe`, e.g. `@GET("/") Observable getUser();`. name in the message reflects which raw type was detected.

Common situations: Migrating from RxJava1 to RxJava2 and dropping generics; suppressing raw-types warnings; copy-paste errors. Surfaces at first method invocation because Retrofit validates lazily.

Related errors


AI-assisted analysis of square/retrofit@d0b112dad0 (2026-08-04). Data as JSON: /data/errors/a4848f3a62ed5b45.json. Report an issue: GitHub.