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 2.x methods declared as e.g. Observable<Response<...>>, the nested Response must be generic so the adapter can extract the body type. A raw Response leaves no deserialization target, so RxJava2CallAdapterFactory throws IllegalStateException when the inner observableType is Response.class but not a ParameterizedType.

Source

Thrown at retrofit-adapters/rxjava2/src/main/java/retrofit2/adapter/rxjava2/RxJava2CallAdapterFactory.java:132

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

View on GitHub (pinned to d0b112dad0)

Solutions

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

Example fix

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

Strategy: validation

Validate before calling

import java.lang.reflect.*;
static void assertRx2ResponseParameterized(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 {
  Single<Response<User>> s = 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 for status/headers and forgetting the body generic; partial refactor.

Related errors


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