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

The RxJava 1.x adapter's Result<T> wrapper packages either a Response or an error into the stream so that network failures are delivered as data rather than via onError. A raw Result (Observable<Result>) gives the adapter no body type to deserialize, so RxJavaCallAdapterFactory throws IllegalStateException when the inner observableType is Result.class but not a ParameterizedType.

Source

Thrown at retrofit-adapters/rxjava/src/main/java/retrofit2/adapter/rxjava/RxJavaCallAdapterFactory.java:134

              + " 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 RxJavaCallAdapter(
        responseType, scheduler, isAsync, isResult, isBody, isSingle, false);
  }
}

View on GitHub (pinned to d0b112dad0)

Solutions

  1. Parameterize Result: `Observable<Result<User>> getUser(@Path("id") long id);`
  2. Switch to Response or body-only if you do not need error-in-stream semantics.

Example fix

// before
Observable<Result> getUser(@Path("id") long id);
// after
Observable<Result<User>> getUser(@Path("id") long id);
Defensive patterns

Strategy: validation

Validate before calling

import java.lang.reflect.*;
static void assertRxResultParameterized(Class<?> iface) {
  for (Method m : iface.getDeclaredMethods()) {
    if (!(m.getGenericReturnType() instanceof ParameterizedType)) continue;
    Type inner = ((ParameterizedType) m.getGenericReturnType()).getActualTypeArguments()[0];
    if (inner.getTypeName().startsWith("retrofit2.adapter.rxjava.Result") && !(inner instanceof ParameterizedType)) {
      throw new AssertionError("Raw nested Result on " + m);
    }
  }
}

Try / catch

try {
  Observable<Result<User>> o = service.getUser(id);
} catch (IllegalStateException e) {
  throw new IllegalStateException("Nested Result return type is raw: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: A method like `Observable<Result> getUser();` — Result used without its type argument. Thrown after `getRawType(observableType) == Result.class` when observableType is not a ParameterizedType.

Common situations: Using Result to swallow network errors in-stream but forgetting the body generic; copy-paste from samples.

Related errors


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