{"record":{"id":"f0362cbd85930eb8","repo":"lysine-dev/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/lysine-dev/retrofit/blob/d0b112dad073b7fe49c953ebc46ff1b424cb1e51/retrofit-adapters/scala/src/main/java/retrofit2/adapter/scala/ScalaCallAdapterFactory.java#L47-L83","documentation":"Thrown by ScalaCallAdapterFactory.get() when a Retrofit service method's return type is a raw scala.concurrent.Future with no type argument. The factory first checks getRawType(returnType) == Future.class, then requires returnType to be a ParameterizedType so it can extract the inner body or Response type. A raw Future leaves the adapter unable to determine what to deserialize.","triggerScenarios":"A Retrofit interface method declared as Future getUser(...) (raw Future return). The factory's `returnType instanceof ParameterizedType` check fails immediately and throws IllegalStateException before any network call.","commonSituations":"Porting a synchronous Call-based method to the Scala adapter and omitting the type argument, or IDE raw-type warnings being ignored because the code still compiles under Java interoperability with Scala.","solutions":["Parameterize the Future: change Future getUser(...) to Future<User> getUser(...).","If you want the full retrofit2.Response, use Future<Response<User>>.","Verify you imported scala.concurrent.Future and not java.util.concurrent.Future, which belongs to a different adapter.","Recompile so Retrofit sees the corrected signature when it eagerly validates the service interface."],"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":"// No per-call runtime pre-check exists; this is a signature discipline error.\n// Catch it in a unit test that builds the Retrofit instance eagerly:\n// Retrofit.Builder().baseUrl(...).addCallAdapterFactory(new ScalaCallAdapterFactory()).build()\n//   .create(ApiService.class);\n//\n// Static check that Future is parameterized:\nMethod m = ApiService.class.getMethod(\"getUser\", String.class);\nType rt = m.getGenericReturnType();\nif (!(rt instanceof ParameterizedType)\n    && m.getReturnType() == scala.concurrent.Future.class) {\n  throw new AssertionError(\"getUser() returns raw Future; parameterize it.\");\n}","typeGuard":"// In Scala the type system already enforces this; in Java interop, declare a typed helper:\n// Prefer the Scala-side trait with an explicit type parameter rather than a raw Java Future.","tryCatchPattern":"// Fires at Retrofit.create() time. Isolate if you cannot fix the signature immediately:\ntry {\n  ApiService api = retrofit.create(ApiService.class);\n} catch (IllegalStateException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"Future return type must be parameterized\")) {\n    throw new ConfigurationException(\"Service interface has a raw Future method\", e);\n  }\n  throw e;\n}","preventionTips":["Always parameterize scala.concurrent.Future with the body type.","Confirm you import scala.concurrent.Future, not java.util.concurrent.Future.","Add a build-time unit test that exercises Retrofit.create() for the Scala-backed service."],"tags":["retrofit","scala","type-parameterization","configuration"],"backgroundTag":null,"analyzedSha":"d0b112dad073b7fe49c953ebc46ff1b424cb1e51","analyzedAt":"2026-08-13T23:49:47.955Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}