{"id":"6179d4843f973539","repo":"square/retrofit","slug":"future-return-type-must-be-parameterized-as-future","errorCode":null,"errorMessage":"Future return type must be parameterized as Future<Foo> or Future<? extends Foo>","messagePattern":"Future return type must be parameterized as Future<Foo> or Future<\\? extends Foo>","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"retrofit-adapters/scala/src/main/java/retrofit2/adapter/scala/ScalaCallAdapterFactory.java","lineNumber":65,"sourceCode":" *   <li>Response wrapped body (e.g., {@code Future<Response<User>>}) returns a {@link Response}\n *       object for all HTTP responses and sets {@link IOException} for network errors\n * </ul>\n */\npublic final class ScalaCallAdapterFactory extends CallAdapter.Factory {\n  public static ScalaCallAdapterFactory create() {\n    return new ScalaCallAdapterFactory();\n  }\n\n  private ScalaCallAdapterFactory() {}\n\n  @Override\n  public @Nullable CallAdapter<?, ?> get(\n      Type returnType, Annotation[] annotations, Retrofit retrofit) {\n    if (getRawType(returnType) != Future.class) {\n      return null;\n    }\n    if (!(returnType instanceof ParameterizedType)) {\n      throw new IllegalStateException(\n          \"Future return type must be parameterized as Future<Foo> or Future<? 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    if (!(innerType instanceof ParameterizedType)) {\n      throw new IllegalStateException(\n          \"Response must be parameterized as Response<Foo> or Response<? extends Foo>\");\n    }\n\n    Type responseType = getParameterUpperBound(0, (ParameterizedType) innerType);\n    return new ResponseCallAdapter<>(responseType);\n  }\n}","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/square/retrofit/blob/d0b112dad073b7fe49c953ebc46ff1b424cb1e51/retrofit-adapters/scala/src/main/java/retrofit2/adapter/scala/ScalaCallAdapterFactory.java#L47-L83","documentation":"Thrown by the Scala call adapter factory when a service method returns the raw scala.concurrent.Future type without a type argument. The factory (ScalaCallAdapterFactory.java:64-67) requires a ParameterizedType so it can resolve the inner body/Response type and build the correct adapter; a raw Future has no body type, so it fails fast with IllegalStateException when the method is first invoked.","triggerScenarios":"A method declared as `@GET(\"x\") Future getUser();` (raw Future). The check fires at line 64 when getRawType(returnType) == Future.class but returnType is not a ParameterizedType.","commonSituations":"Java/Scala interop where the generic was dropped; copy-paste from a sample that omitted the type parameter; IDE raw-type import of scala.concurrent.Future; migrating a method from Call<T> to Future and forgetting the argument.","solutions":["Parameterize Future with the body type: `Future<User> getUser()`.","If you need headers/status, wrap as `Future<Response<User>>`.","Avoid using raw Future from Java interop; declare the full generic signature explicitly."],"exampleFix":"// before\n@GET(\"users/{id}\")\nFuture getUser(@Path(\"id\") String id);\n\n// after\n@GET(\"users/{id}\")\nFuture<User> getUser(@Path(\"id\") String id);","handlingStrategy":"validation","validationCode":"// Startup smoke test asserting scala Future is never used raw.\n@Test void futureReturnTypesAreParameterized() {\n  for (java.lang.reflect.Method m : MyService.class.getDeclaredMethods()) {\n    if (m.isDefault()) continue;\n    Type t = m.getGenericReturnType();\n    assertFalse(\n        t == scala.concurrent.Future.class,\n        m.getName() + \" returns raw Future; use Future<BodyType>\");\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  MyService service = retrofit.create(MyService.class);\n  // force method resolution via a startup smoke test\n} catch (IllegalStateException e) {\n  throw new IllegalStateException(\n      \"Retrofit service method return type is invalid: \" + e.getMessage(), e);\n}","preventionTips":["Always declare the full generic signature: Future<User>, never raw Future.","From Java interop, never let type inference drop the generic argument.","Keep an integration test that resolves every service method at build time.","Treat raw-type warnings as errors in your IDE/compiler."],"tags":["scala","retrofit","generics","java","call-adapter"],"analyzedSha":"d0b112dad073b7fe49c953ebc46ff1b424cb1e51","analyzedAt":"2026-08-04T19:12:59.096Z","schemaVersion":2}