square/retrofit · error · NullPointerException

scheduler == null

Error message

scheduler == null

What it means

`RxJava3CallAdapterFactory.createWithScheduler(Scheduler)` at RxJava3CallAdapterFactory.java:86 requires a non-null Scheduler because every stream produced will subscribeOn it. A null scheduler would NPE inside RxJava 3 internals, so the factory fails fast at construction with a clear message.

Source

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

    return new RxJava3CallAdapterFactory(null, true);
  }

  /**
   * Returns an instance which creates synchronous observables that do not operate on any scheduler
   * by default. Applying {@code subscribeOn(..)} will change the scheduler on which the HTTP calls
   * are made.
   */
  public static RxJava3CallAdapterFactory createSynchronous() {
    return new RxJava3CallAdapterFactory(null, false);
  }

  /**
   * Returns an instance which creates synchronous observables that {@code subscribeOn(..)} the
   * supplied {@code scheduler} by default.
   */
  @SuppressWarnings("ConstantConditions") // Guarding public API nullability.
  public static RxJava3CallAdapterFactory createWithScheduler(Scheduler scheduler) {
    if (scheduler == null) throw new NullPointerException("scheduler == null");
    return new RxJava3CallAdapterFactory(scheduler, false);
  }

  private final @Nullable Scheduler scheduler;
  private final boolean isAsync;

  private RxJava3CallAdapterFactory(@Nullable Scheduler scheduler, boolean isAsync) {
    this.scheduler = scheduler;
    this.isAsync = isAsync;
  }

  @Override
  public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    Class<?> rawType = getRawType(returnType);

    if (rawType == Completable.class) {
      // Completable is not parameterized (which is what the rest of this method deals with) so it

View on GitHub (pinned to d0b112dad0)

Solutions

  1. Pass a concrete Scheduler: `RxJava3CallAdapterFactory.createWithScheduler(io.reactivex.rxjava3.core.Schedulers.io())`.
  2. If you do not need a default scheduler, use `create()` (async default) or `createSynchronous()`.
  3. Initialize the scheduler before constructing the factory.

Example fix

// before
Retrofit r = new Retrofit.Builder()
    .addCallAdapterFactory(RxJava3CallAdapterFactory.createWithScheduler(resolveScheduler()))
    .build(); // null -> NPE

// after
Scheduler s = resolveScheduler();
if (s == null) s = io.reactivex.rxjava3.core.Schedulers.io();
Retrofit r = new Retrofit.Builder()
    .addCallAdapterFactory(RxJava3CallAdapterFactory.createWithScheduler(s))
    .build();
Defensive patterns

Strategy: validation

Validate before calling

Scheduler s = resolveSchedulerFromConfig();
if (s == null) throw new IllegalArgumentException("scheduler config missing");
RxJava3CallAdapterFactory factory = RxJava3CallAdapterFactory.createWithScheduler(s);

Try / catch

RxJava3CallAdapterFactory factory;
try {
    factory = RxJava3CallAdapterFactory.createWithScheduler(resolveScheduler());
} catch (NullPointerException e) {
    if (e.getMessage().equals("scheduler == null")) {
        factory = RxJava3CallAdapterFactory.create();
    } else throw e;
}

Prevention

When it happens

Trigger: Calling `RxJava3CallAdapterFactory.createWithScheduler(null)` while building the Retrofit instance.

Common situations: Passing an uninitialized Scheduler field; resolving a scheduler from config that returned null; refactoring from create()/createSynchronous() and forgetting the argument.

Related errors


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