{"record":{"id":"b5b8911d7e58e02a","repo":"lysine-dev/retrofit","slug":"completablefuture-return-type-must-be-parameterize","errorCode":null,"errorMessage":"CompletableFuture return type must be parameterized as CompletableFuture<Foo> or CompletableFuture<? extends Foo>","messagePattern":"CompletableFuture return type must be parameterized as CompletableFuture<Foo> or CompletableFuture<\\? extends Foo>","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"retrofit-adapters/java8/src/main/java/retrofit2/adapter/java8/Java8CallAdapterFactory.java","lineNumber":67,"sourceCode":" *           errors\n *     </ul>\n */\n@Deprecated\npublic final class Java8CallAdapterFactory extends CallAdapter.Factory {\n  public static Java8CallAdapterFactory create() {\n    return new Java8CallAdapterFactory();\n  }\n\n  private Java8CallAdapterFactory() {}\n\n  @Override\n  public @Nullable CallAdapter<?, ?> get(\n      Type returnType, Annotation[] annotations, Retrofit retrofit) {\n    if (getRawType(returnType) != CompletableFuture.class) {\n      return null;\n    }\n    if (!(returnType instanceof ParameterizedType)) {\n      throw new IllegalStateException(\n          \"CompletableFuture return type must be parameterized\"\n              + \" as CompletableFuture<Foo> or CompletableFuture<? 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  }","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/lysine-dev/retrofit/blob/d0b112dad073b7fe49c953ebc46ff1b424cb1e51/retrofit-adapters/java8/src/main/java/retrofit2/adapter/java8/Java8CallAdapterFactory.java#L49-L85","documentation":"Retrofit's Java 8 adapter handles methods returning java.util.concurrent.CompletableFuture, but only when the return type is parameterized with an element type. A raw CompletableFuture has no type information, so Java8CallAdapterFactory cannot determine the body type to deserialize and throws IllegalStateException during call-adapter resolution.","triggerScenarios":"Declaring a method with a raw return type such as `CompletableFuture getUser();`. Thrown on first invocation of that method via the Retrofit proxy, inside Java8CallAdapterFactory.get() when `returnType` is not a ParameterizedType.","commonSituations":"Switching from `Call<T>` to CompletableFuture and omitting the generic; IDE inserting the raw type; using the java8 adapter on a project that previously returned `Call`.","solutions":["Parameterize the return type with the body type: `CompletableFuture<User> getUser(@Path(\"id\") long id);`","For full HTTP responses: `CompletableFuture<Response<User>> getUser(@Path(\"id\") long id);`","Remove raw-types suppressions and resolve every compiler warning."],"exampleFix":"// before\nCompletableFuture getUser(@Path(\"id\") long id);\n// after\nCompletableFuture<User> getUser(@Path(\"id\") long id);","handlingStrategy":"validation","validationCode":"import java.lang.reflect.*;\nstatic void assertFuturesParameterized(Class<?> iface) {\n  for (Method m : iface.getDeclaredMethods()) {\n    Type rt = m.getGenericReturnType();\n    if (rt.getTypeName().startsWith(\"java.util.concurrent.CompletableFuture\")\n        && !(rt instanceof ParameterizedType)) {\n      throw new AssertionError(\"Raw CompletableFuture return type on \" + m);\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  MyService svc = retrofit.create(MyService.class);\n  CompletableFuture<User> f = svc.getUser(id);\n} catch (IllegalStateException e) {\n  throw new IllegalStateException(\"Service interface has a raw CompletableFuture return type: \" + e.getMessage(), e);\n}","preventionTips":["Build with -Xlint:rawtypes to fail on raw types.","Add a unit test calling assertFuturesParameterized(MyService.class).","Do not suppress raw-types warnings on service interfaces."],"tags":["retrofit","java8","generics","java","type-system","completablefuture"],"backgroundTag":null,"analyzedSha":"d0b112dad073b7fe49c953ebc46ff1b424cb1e51","analyzedAt":"2026-08-13T23:49:47.955Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}