square/retrofit · error · NullPointerException

scheduler == null

Error message

scheduler == null

What it means

`RxJavaCallAdapterFactory.createWithScheduler(Scheduler)` at RxJavaCallAdapterFactory.java:83 requires a non-null Scheduler because the factory will subscribeOn the supplied scheduler for every stream produced. A null scheduler would NPE later inside RxJava internals with a far less obvious message, so the factory fails fast at construction.

Source

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

   * Returns an instance which creates synchronous observables that do not operate on any scheduler
   * by default.
   */
  public static RxJavaCallAdapterFactory create() {
    return new RxJavaCallAdapterFactory(null, false);
  }

  /** Returns an instance which creates asynchronous observables. */
  public static RxJavaCallAdapterFactory createAsync() {
    return new RxJavaCallAdapterFactory(null, true);
  }

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

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

  private RxJavaCallAdapterFactory(@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);
    boolean isSingle = rawType == Single.class;
    boolean isCompletable = rawType == Completable.class;
    if (rawType != Observable.class && !isSingle && !isCompletable) {

View on GitHub (pinned to d0b112dad0)

Solutions

  1. Pass a concrete Scheduler, e.g. `RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io())`.
  2. If you do not need a default scheduler, use `create()` (synchronous) or `createAsync()` (async) instead.
  3. Initialize the scheduler field before constructing the factory.

Example fix

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

// after
Scheduler s = resolveScheduler();
if (s == null) s = rx.schedulers.Schedulers.io();
Retrofit r = new Retrofit.Builder()
    .addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(s))
    .build();
Defensive patterns

Strategy: validation

Validate before calling

// Validate before passing to the factory:
Scheduler s = resolveSchedulerFromConfig();
if (s == null) throw new IllegalArgumentException("scheduler config missing; defaulting is disabled");
RxJavaCallAdapterFactory factory = RxJavaCallAdapterFactory.createWithScheduler(s);

Try / catch

// Wrap factory construction; default to create() if no scheduler:
RxJavaCallAdapterFactory factory;
try {
    factory = RxJavaCallAdapterFactory.createWithScheduler(resolveScheduler());
} catch (NullPointerException e) {
    if (e.getMessage().equals("scheduler == null")) {
        factory = RxJavaCallAdapterFactory.create(); // synchronous fallback
    } else throw e;
}

Prevention

When it happens

Trigger: Calling `RxJavaCallAdapterFactory.createWithScheduler(null)` when building the Retrofit instance.

Common situations: Passing a Scheduler field that has not been initialized yet; loading a scheduler name from config, resolving to null; or refactoring from create() to createWithScheduler() and forgetting to supply an argument.

Related errors


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