square/retrofit · error · IllegalStateException
Response must be parameterized as 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.
Solutions
- Parameterize Response with the body type: `Observable<Response<User>> getUser(...)`.
- If you only need the body, drop the wrapper entirely: `Observable<User>`.
- 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
- Always specify the inner type when wrapping with Response, e.g. Observable<Response<User>>.
- Keep a MockWebServer integration test that exercises every service method at build time.
- Treat IDE raw-type warnings as errors.
- In Kotlin, annotate platform-typed returns explicitly to avoid generic elision.
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
- Result must be parameterized as Result
- Future return type must be parameterized as Future
- return type must be parameterized as or <? extends Foo>
- Response must be parameterized as Response
- error == null
AI-assisted analysis of square/retrofit@d0b112dad0 (2026-08-04).
Data as JSON: /api/errors/18ee2568241a0e5c.
Report an issue: GitHub.
Appendix: 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)