lysine-dev/retrofit · error · IllegalStateException
Future return type must be parameterized as Future<Foo> or F
Error message
Future return type must be parameterized as Future<Foo> or Future<? extends Foo>
What it means
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.
Source
Thrown at retrofit-adapters/scala/src/main/java/retrofit2/adapter/scala/ScalaCallAdapterFactory.java:65
* <li>Response wrapped body (e.g., {@code Future<Response<User>>}) returns a {@link Response}
* object for all HTTP responses and sets {@link IOException} for network errors
* </ul>
*/
public final class ScalaCallAdapterFactory extends CallAdapter.Factory {
public static ScalaCallAdapterFactory create() {
return new ScalaCallAdapterFactory();
}
private ScalaCallAdapterFactory() {}
@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
- 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.
Example fix
// before
@GET("users/{id}")
Future getUser(@Path("id") String id);
// after
@GET("users/{id}")
Future<User> getUser(@Path("id") String id); Defensive patterns
Strategy: validation
Validate before calling
// No per-call runtime pre-check exists; this is a signature discipline error.
// Catch it in a unit test that builds the Retrofit instance eagerly:
// Retrofit.Builder().baseUrl(...).addCallAdapterFactory(new ScalaCallAdapterFactory()).build()
// .create(ApiService.class);
//
// Static check that Future is parameterized:
Method m = ApiService.class.getMethod("getUser", String.class);
Type rt = m.getGenericReturnType();
if (!(rt instanceof ParameterizedType)
&& m.getReturnType() == scala.concurrent.Future.class) {
throw new AssertionError("getUser() returns raw Future; parameterize it.");
} Type guard
// In Scala the type system already enforces this; in Java interop, declare a typed helper: // Prefer the Scala-side trait with an explicit type parameter rather than a raw Java Future.
Try / catch
// Fires at Retrofit.create() time. Isolate if you cannot fix the signature immediately:
try {
ApiService api = retrofit.create(ApiService.class);
} catch (IllegalStateException e) {
if (e.getMessage() != null && e.getMessage().contains("Future return type must be parameterized")) {
throw new ConfigurationException("Service interface has a raw Future method", e);
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Response must be parameterized as Response<Foo> or Response<
- Response must be parameterized as Response<Foo> or Response<
- Result must be parameterized as Result<Foo> or Result<? exte
- gson == null
- mapper == null
AI-assisted analysis of lysine-dev/retrofit@d0b112dad0 (2026-08-13).
Data as JSON: /api/errors/f0362cbd85930eb8.
Report an issue: GitHub.