lysine-dev/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

For RxJava 1.x methods declared as Observable<Response<...>> (or Single<Response<...>>), the nested Response must be generic so the adapter can extract the body type. A raw Response gives no deserialization target, so RxJavaCallAdapterFactory throws IllegalStateException when the inner observableType is Response.class but not a ParameterizedType.

Source

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

    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) {
      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 RxJavaCallAdapter(
        responseType, scheduler, isAsync, isResult, isBody, isSingle, false);
  }

View on GitHub (pinned to d0b112dad0)

Solutions

  1. Parameterize Response: `Observable<Response<User>> getUser(@Path("id") long id);`
  2. Drop Response for body-only: `Observable<User> getUser(@Path("id") long id);`

Example fix

// before
Observable<Response> getUser(@Path("id") long id);
// after
Observable<Response<User>> getUser(@Path("id") long id);
Defensive patterns

Strategy: validation

Validate before calling

import java.lang.reflect.*;
static void assertRxResponseParameterized(Class<?> iface) {
  for (Method m : iface.getDeclaredMethods()) {
    if (!(m.getGenericReturnType() instanceof ParameterizedType)) continue;
    Type inner = ((ParameterizedType) m.getGenericReturnType()).getActualTypeArguments()[0];
    if (inner.getTypeName().startsWith("retrofit2.Response") && !(inner instanceof ParameterizedType)) {
      throw new AssertionError("Raw nested Response on " + m);
    }
  }
}

Try / catch

try {
  Observable<Response<User>> o = service.getUser(id);
} catch (IllegalStateException e) {
  throw new IllegalStateException("Nested Response return type is raw: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: A method like `Observable<Response> getUser();` — outer type parameterized, inner Response raw. Thrown after `getRawType(observableType) == Response.class` when observableType is not a ParameterizedType.

Common situations: Adding the Response wrapper to access status codes and forgetting the body generic; refactor dropping inner angle brackets.

Related errors


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