lysine-dev/retrofit · error · IllegalStateException

Result must be parameterized as Result

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.

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.

Appendix: 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)