square/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 the RxJava3 call adapter factory when a service method returns an RxJava type parameterized with the raw retrofit2.adapter.rxjava3.Result class (no type argument). Result is Retrofit's wrapper that emits both Response and errors in the stream, but the factory needs the inner body type (line 146) to construct the adapter, so a raw Result is rejected at method-resolution time as an IllegalStateException.

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: `Observable<Result<User>>`.
  2. If you do not need error-as-value semantics, use `Observable<User>` or `Observable<Response<User>>` instead.
  3. Remember Result only emits one item (Response or error) and never both, so the inner type is mandatory.

Example fix

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

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

Strategy: validation

Validate before calling

// Startup smoke test asserting Result is never used raw.
@Test void resultReturnTypesAreParameterized() throws Exception {
  Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost/")
      .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
      .build();
  retrofit.create(MyService.class);
  for (java.lang.reflect.Method m : MyService.class.getDeclaredMethods()) {
    if (m.isDefault()) continue;
    Type t = m.getGenericReturnType();
    if (t instanceof java.lang.reflect.ParameterizedType) {
      Type inner = ((java.lang.reflect.ParameterizedType) t).getActualTypeArguments()[0];
      assertNotEquals(retrofit2.adapter.rxjava3.Result.class, inner,
          m.getName() + " uses raw Result; use Result<BodyType>");
    }
  }
}

Try / catch

try {
  MyService service = retrofit.create(MyService.class);
  // exercise each method 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") Observable<Result> get();` (raw Result). The check at RxJava3CallAdapterFactory.java:141-145 fires when rawObservableType == Result.class but observableType is not a ParameterizedType.

Common situations: Developer switches from `Observable<User>` to the Result wrapper to capture HttpException/network errors in onNext but forgets the inner type; mixing up retrofit2.Response and the RxJava3 Result wrapper; copy-paste error; Kotlin generic elision.

Related errors


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