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
When a Guava-adapter method returns ListenableFuture<Response<...>>, the inner Response must itself be generic. A raw Response gives the adapter no body type to deserialize, so GuavaCallAdapterFactory throws IllegalStateException while trying to extract the response type parameter from the Response. The outer ListenableFuture is fine; only the nested Response is missing its type argument.
Source
Thrown at retrofit-adapters/guava/src/main/java/retrofit2/adapter/guava/GuavaCallAdapterFactory.java:81
Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != ListenableFuture.class) {
return null;
}
if (!(returnType instanceof ParameterizedType)) {
throw new IllegalStateException(
"ListenableFuture return type must be parameterized"
+ " as ListenableFuture<Foo> or ListenableFuture<? extends Foo>");
}
Type innerType = getParameterUpperBound(0, (ParameterizedType) returnType);
if (getRawType(innerType) != Response.class) {
// Generic type is not Response<T>. Use it for body-only adapter.
return new BodyCallAdapter<>(innerType);
}
// Generic type is Response<T>. Extract T and create the Response version of the adapter.
if (!(innerType instanceof ParameterizedType)) {
throw new IllegalStateException(
"Response must be parameterized" + " as Response<Foo> or Response<? extends Foo>");
}
Type responseType = getParameterUpperBound(0, (ParameterizedType) innerType);
return new ResponseCallAdapter<>(responseType);
}
private static final class BodyCallAdapter<R> implements CallAdapter<R, ListenableFuture<R>> {
private final Type responseType;
BodyCallAdapter(Type responseType) {
this.responseType = responseType;
}
@Override
public Type responseType() {
return responseType;
}
View on GitHub (pinned to d0b112dad0)
Solutions
- Parameterize Response with the body type: `ListenableFuture<Response<User>> getUser(@Path("id") long id);`
- If you only need the body, drop Response entirely: `ListenableFuture<User> getUser(@Path("id") long id);`
Example fix
// before
ListenableFuture<Response> getUser(@Path("id") long id);
// after
ListenableFuture<Response<User>> getUser(@Path("id") long id); Defensive patterns
Strategy: validation
Validate before calling
// Assert any Response nested inside a ListenableFuture return type is itself parameterized.
import java.lang.reflect.*;
static void assertResponsesParameterized(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 Response inside return type of " + m);
}
}
} Try / catch
try {
ListenableFuture<Response<User>> f = service.getUser(id);
} catch (IllegalStateException e) {
throw new IllegalStateException("Nested Response return type is raw on the service interface: " + e.getMessage(), e);
} Prevention
- Treat raw `Response` as a build error via -Xlint:rawtypes.
- Add a reflection-based unit test asserting nested Response types are parameterized.
- Prefer the body-only form unless you actually need status/headers.
When it happens
Trigger: A method declared as `ListenableFuture<Response> getUser();` where the outer type is parameterized but the inner Response is raw. Thrown in get() when `innerType` (the Response) is not a ParameterizedType after `getRawType(innerType) == Response.class`.
Common situations: Adding the Response wrapper to access status codes/headers but forgetting the body generic; a refactor that drops the inner angle brackets; copy-paste from sample code that used `Response`.
Related errors
- ListenableFuture return type must be parameterized as Listen
- Response must be parameterized as Response<Foo> or Response<
- Response must be parameterized as Response<Foo> or Response<
- Response must be parameterized as Response<Foo> or Response<
- CompletableFuture return type must be parameterized as Compl
AI-assisted analysis of lysine-dev/retrofit@d0b112dad0 (2026-08-13).
Data as JSON: /api/errors/03f2064776430fef.
Report an issue: GitHub.