lysine-dev/retrofit · error · IllegalStateException

Result must be parameterized as Result<Foo> or Result<? exte

Error message

Result must be parameterized as Result<Foo> or Result<? extends Foo>

What it means

Thrown by RxJava3CallAdapterFactory when the reactive return type's argument is a raw retrofit2.adapter.rxjava3.Result. Result<T> is this adapter's wrapper that emits either the Response body or a thrown exception so subscribers receive both as stream values. The factory needs Result parameterized so it can resolve the inner body type to deserialize.

Source

Thrown at retrofit-adapters/rxjava3/src/main/java/retrofit2/adapter/rxjava3/RxJava3CallAdapterFactory.java:143

              + " return type must be parameterized"
              + " as "
              + name
              + "<Foo> or "
              + name
              + "<? extends Foo>");
    }

    Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
    Class<?> rawObservableType = getRawType(observableType);
    if (rawObservableType == Response.class) {
      if (!(observableType instanceof ParameterizedType)) {
        throw new IllegalStateException(
            "Response must be parameterized" + " as Response<Foo> or Response<? extends Foo>");
      }
      responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
    } else if (rawObservableType == Result.class) {
      if (!(observableType instanceof ParameterizedType)) {
        throw new IllegalStateException(
            "Result must be parameterized" + " as Result<Foo> or Result<? extends Foo>");
      }
      responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
      isResult = true;
    } else {
      responseType = observableType;
      isBody = true;
    }

    return new RxJava3CallAdapter(
        responseType, scheduler, isAsync, isResult, isBody, isFlowable, isSingle, isMaybe, false);
  }
}

View on GitHub (pinned to d0b112dad0)

Solutions

  1. Parameterize Result with the body type: change Observable<Result> to Observable<Result<User>>.
  2. If you actually want the raw retrofit2.Response, use Observable<Response<User>> instead of Result.
  3. If you only need the body and prefer exceptions to propagate down onError, return Observable<User> directly.
  4. Recompile the module so the corrected signature is picked up at Retrofit.validateEagerly() time.

Example fix

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

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

Strategy: validation

Validate before calling

// The guard is method-signature discipline; there is no per-call runtime pre-check.
// Verify at test time that Result is parameterized:
Method m = ApiService.class.getMethod("getUser", String.class);
Type rt = m.getGenericReturnType();
if (rt instanceof ParameterizedType) {
  Type arg = ((ParameterizedType) rt).getActualTypeArguments()[0];
  if (arg == retrofit2.adapter.rxjava3.Result.class) {
    throw new AssertionError("getUser() returns raw Observable<Result>; parameterize it.");
  }
}

Type guard

// Kotlin reified helper so Result is always parameterized at the call site.
inline fun <reified T> resultObservableType(): Type =
    TypeToken<Observable<Result<T>>>() {}.type

Try / catch

// Fires at Retrofit.create() time. Prefer fixing the signature; isolate like this if needed:
try {
  ApiService api = retrofit.create(ApiService.class);
} catch (IllegalStateException e) {
  if (e.getMessage() != null && e.getMessage().contains("Result must be parameterized")) {
    throw new ConfigurationException("Service interface has a raw Observable<Result> method", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: A Retrofit interface method declared as Observable<Result>, Flowable<Result>, Single<Result>, or Maybe<Result> (raw Result). During Retrofit.build()/service-interface scanning the branch `rawObservableType == Result.class` runs, finds observableType is a Class not a ParameterizedType, and throws.

Common situations: Switching a stream from body-only Observable<User> to error-capturing Observable<Result> and forgetting the generic, or misreading the Result class javadoc and assuming it is self-describing without a type argument.

Related errors


AI-assisted analysis of lysine-dev/retrofit@d0b112dad0 (2026-08-13). Data as JSON: /api/errors/5657e8d26c1484b3. Report an issue: GitHub.