{"record":{"id":"03f2064776430fef","repo":"lysine-dev/retrofit","slug":"response-must-be-parameterized-as-response-foo-or","errorCode":null,"errorMessage":"Response must be parameterized as Response<Foo> or Response<? extends Foo>","messagePattern":"Response must be parameterized as Response<Foo> or Response<\\? extends Foo>","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"retrofit-adapters/guava/src/main/java/retrofit2/adapter/guava/GuavaCallAdapterFactory.java","lineNumber":81,"sourceCode":"      Type returnType, Annotation[] annotations, Retrofit retrofit) {\n    if (getRawType(returnType) != ListenableFuture.class) {\n      return null;\n    }\n    if (!(returnType instanceof ParameterizedType)) {\n      throw new IllegalStateException(\n          \"ListenableFuture return type must be parameterized\"\n              + \" as ListenableFuture<Foo> or ListenableFuture<? extends Foo>\");\n    }\n    Type innerType = getParameterUpperBound(0, (ParameterizedType) returnType);\n\n    if (getRawType(innerType) != Response.class) {\n      // Generic type is not Response<T>. Use it for body-only adapter.\n      return new BodyCallAdapter<>(innerType);\n    }\n\n    // Generic type is Response<T>. Extract T and create the Response version of the adapter.\n    if (!(innerType instanceof ParameterizedType)) {\n      throw new IllegalStateException(\n          \"Response must be parameterized\" + \" as Response<Foo> or Response<? extends Foo>\");\n    }\n    Type responseType = getParameterUpperBound(0, (ParameterizedType) innerType);\n    return new ResponseCallAdapter<>(responseType);\n  }\n\n  private static final class BodyCallAdapter<R> implements CallAdapter<R, ListenableFuture<R>> {\n    private final Type responseType;\n\n    BodyCallAdapter(Type responseType) {\n      this.responseType = responseType;\n    }\n\n    @Override\n    public Type responseType() {\n      return responseType;\n    }\n","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/lysine-dev/retrofit/blob/d0b112dad073b7fe49c953ebc46ff1b424cb1e51/retrofit-adapters/guava/src/main/java/retrofit2/adapter/guava/GuavaCallAdapterFactory.java#L63-L99","documentation":"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.","triggerScenarios":"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`.","commonSituations":"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`.","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);`"],"exampleFix":"// before\nListenableFuture<Response> getUser(@Path(\"id\") long id);\n// after\nListenableFuture<Response<User>> getUser(@Path(\"id\") long id);","handlingStrategy":"validation","validationCode":"// Assert any Response nested inside a ListenableFuture return type is itself parameterized.\nimport java.lang.reflect.*;\nstatic void assertResponsesParameterized(Class<?> iface) {\n  for (Method m : iface.getDeclaredMethods()) {\n    if (!(m.getGenericReturnType() instanceof ParameterizedType)) continue;\n    Type inner = ((ParameterizedType) m.getGenericReturnType()).getActualTypeArguments()[0];\n    if (inner.getTypeName().startsWith(\"retrofit2.Response\") && !(inner instanceof ParameterizedType)) {\n      throw new AssertionError(\"Raw Response inside return type of \" + m);\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  ListenableFuture<Response<User>> f = service.getUser(id);\n} catch (IllegalStateException e) {\n  throw new IllegalStateException(\"Nested Response return type is raw on the service interface: \" + e.getMessage(), e);\n}","preventionTips":["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."],"tags":["retrofit","guava","generics","java","type-system","response"],"backgroundTag":null,"analyzedSha":"d0b112dad073b7fe49c953ebc46ff1b424cb1e51","analyzedAt":"2026-08-13T23:49:47.955Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}