square/retrofit · error · IllegalStateException

Response must be parameterized as Response<Foo> or Response<

Error message

Response must be parameterized as Response<Foo> or Response<? extends Foo>

What it means

Thrown by the RxJava3 call adapter factory while Retrofit resolves a service method's return type: the outer RxJava type (Observable/Flowable/Single/Maybe) is parameterized with the raw retrofit2.Response class instead of Response<T>. Because the factory must extract the body type to build a CallAdapter (see getParameterUpperBound at line 140), a raw Response has no body type to extract, so it fails fast. It surfaces as IllegalStateException the first time the offending method is invoked through the Retrofit proxy.

Source

Thrown at retrofit-adapters/rxjava3/src/main/java/retrofit2/adapter/rxjava3/RxJava3CallAdapterFactory.java:137

    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) {
      if (!(observableType instanceof ParameterizedType)) {
        throw new IllegalStateException(
            "Result must be parameterized" + " as Result<Foo> or Result<? extends Foo>");
      }
      responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
      isResult = true;
    } else {
      responseType = observableType;
      isBody = true;
    }

    return new RxJava3CallAdapter(
        responseType, scheduler, isAsync, isResult, isBody, isFlowable, isSingle, isMaybe, false);
  }

View on GitHub (pinned to d0b112dad0)

Solutions

  1. Parameterize Response with the body type: `Observable<Response<User>> getUser(...)`.
  2. If you only need the body, drop the wrapper entirely: `Observable<User>`.
  3. If you want both success and error emitted into the stream, use `Observable<Result<User>>` instead.

Example fix

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

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

Strategy: validation

Validate before calling

// Startup smoke test: force resolution of each service method's return type
// so raw-Response misuse fails at build/init time, not at the first user request.
@Test void serviceReturnTypesAreParameterized() throws Exception {
  Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost/")
      .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
      .build();
  MyService svc = retrofit.create(MyService.class);
  for (java.lang.reflect.Method m : MyService.class.getDeclaredMethods()) {
    if (m.isDefault()) continue;
    Type t = m.getGenericReturnType();
    if (t instanceof java.lang.reflect.ParameterizedType) {
      Type inner = ((java.lang.reflect.ParameterizedType) t).getActualTypeArguments()[0];
      assertNotEquals(retrofit2.Response.class, inner,
          m.getName() + " uses raw Response; use Response<BodyType>");
    }
  }
}

Try / catch

// Wrap retrofit.create() / first invocation at app init to fail fast with a
// clear, attributable message. (Retrofit resolves return types lazily on
// first proxy invocation, so force it during startup validation.)
try {
  MyService service = retrofit.create(MyService.class);
  // touch each method's return-type resolution via your smoke test
} catch (IllegalStateException e) {
  throw new IllegalStateException(
      "Retrofit service method has an invalid return type: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: A service method declared as `@GET("users/{id}") Observable<Response> getUser(@Path("id") String id);` (raw Response, no type argument). The check at RxJava3CallAdapterFactory.java:135-139 fires when rawObservableType == Response.class but observableType is not a ParameterizedType.

Common situations: Developer wraps a working `Observable<User>` in Response to gain access to headers/status codes but forgets the inner type argument; IDE auto-imports the raw retrofit2.Response; copy-paste from samples that elided generics; Kotlin platform types where the generic was omitted.

Related errors


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