square/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

Thrown by the Scala call adapter factory when the inner type of a scala.concurrent.Future is the raw retrofit2.Response class (no type argument). After confirming the Future's inner raw type is Response (line 70), the factory requires it to be parameterized (line 75) so it can extract the body type at line 80; a raw Response is rejected with IllegalStateException.

Source

Thrown at retrofit-adapters/scala/src/main/java/retrofit2/adapter/scala/ScalaCallAdapterFactory.java:76

  @Override
  public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Future.class) {
      return null;
    }
    if (!(returnType instanceof ParameterizedType)) {
      throw new IllegalStateException(
          "Future return type must be parameterized as Future<Foo> or Future<? 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);
    }

    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);
  }
}

View on GitHub (pinned to d0b112dad0)

Solutions

  1. Parameterize the inner Response: `Future<Response<User>> getUser()`.
  2. If you only need the body, use `Future<User>` directly.
  3. Double-check the import is retrofit2.Response and not another Response class, then add the type argument.

Example fix

// before
@GET("users/{id}")
Future<Response> getUser(@Path("id") String id);

// after
@GET("users/{id}")
Future<Response<User>> getUser(@Path("id") String id);
Defensive patterns

Strategy: validation

Validate before calling

// Startup smoke test asserting the inner type of Future is never raw Response.
@Test void futureInnerResponseIsParameterized() throws Exception {
  for (java.lang.reflect.Method m : MyService.class.getDeclaredMethods()) {
    if (m.isDefault()) continue;
    Type t = m.getGenericReturnType();
    if (!(t instanceof java.lang.reflect.ParameterizedType)) continue;
    Type inner = ((java.lang.reflect.ParameterizedType) t).getActualTypeArguments()[0];
    assertNotEquals(retrofit2.Response.class, inner,
        m.getName() + " uses raw Response inside Future; use Future<Response<BodyType>>");
  }
}

Try / catch

try {
  MyService service = retrofit.create(MyService.class);
  // exercise methods in a startup smoke test to trigger resolution
} catch (IllegalStateException e) {
  throw new IllegalStateException(
      "Retrofit service method return type is invalid: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: A method declared as `@GET("x") Future<Response> getUser();` (raw inner Response). The check fires at ScalaCallAdapterFactory.java:75-78 when getRawType(innerType) == Response.class but innerType is not a ParameterizedType.

Common situations: Developer wraps Future in Response to access HTTP metadata but forgets the inner type argument; Java/Scala interop dropping the generic; IDE raw import of retrofit2.Response; copy-paste from samples.

Related errors


AI-assisted analysis of square/retrofit@d0b112dad0 (2026-08-04). Data as JSON: /data/errors/956be5603339ea24.json. Report an issue: GitHub.