ReactiveX/RxAndroid · error · NullPointerException
run == null
Error message
run == null
What it means
HandlerScheduler.scheduleDirect(Runnable, long, TimeUnit) throws this NullPointerException when the run argument is null (a null unit gets its own check on the next line). HandlerScheduler is what AndroidSchedulers.from(looper) and AndroidSchedulers.main() return, so this is the direct-scheduling entry point of the Android schedulers. Since RxJava operators never pass null runnables, this NPE comes from calling the scheduler API directly with null.
Source
Thrown at rxandroid/src/main/java/io/reactivex/rxjava3/android/schedulers/HandlerScheduler.java:36
import android.os.Message;
import io.reactivex.rxjava3.core.Scheduler;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
import java.util.concurrent.TimeUnit;
final class HandlerScheduler extends Scheduler {
private final Handler handler;
private final boolean async;
HandlerScheduler(Handler handler, boolean async) {
this.handler = handler;
this.async = async;
}
@Override
@SuppressLint("NewApi") // Async will only be true when the API is available to call.
public Disposable scheduleDirect(Runnable run, long delay, TimeUnit unit) {
if (run == null) throw new NullPointerException("run == null");
if (unit == null) throw new NullPointerException("unit == null");
run = RxJavaPlugins.onSchedule(run);
ScheduledRunnable scheduled = new ScheduledRunnable(handler, run);
Message message = Message.obtain(handler, scheduled);
if (async) {
message.setAsynchronous(true);
}
handler.sendMessageDelayed(message, unit.toMillis(delay));
return scheduled;
}
@Override
public Worker createWorker() {
return new HandlerWorker(handler, async);
}
private static final class HandlerWorker extends Worker {View on GitHub (pinned to afaea28046)
Solutions
- Pass a real Runnable: scheduleDirect(() -> doWork(), 0, TimeUnit.MILLISECONDS).
- Null-check before scheduling and fail with your own message: Objects.requireNonNull(run, "task").
- In Kotlin, hold the task as () -> Unit (non-null) and pass it as Runnable; the type system then rejects null.
- If the null comes from a wrapper/decorator, fix the decorator to validate and forward, not silently pass null.
Example fix
// before Runnable task = maybeTask; // null in some path AndroidSchedulers.main().scheduleDirect(task, 200, TimeUnit.MILLISECONDS); // NPE: run == null // after Objects.requireNonNull(maybeTask, "task").run(); AndroidSchedulers.main().scheduleDirect(maybeTask, 200, TimeUnit.MILLISECONDS);
Defensive patterns
Strategy: validation
Validate before calling
if (run == null || unit == null) {
throw new IllegalArgumentException("run and unit are required");
}
AndroidSchedulers.main().scheduleDirect(run, delay, unit); Type guard
public static boolean isSchedulable(Runnable r) {
return r != null;
} Prevention
- Construct the Runnable fully before scheduling; never pass a field that may be uninitialized.
- In Kotlin, use non-null () -> Unit / Runnable types so null cannot reach the scheduler.
- Prefer observable operators (Observable.timer, observeOn) over manual scheduleDirect calls — they never pass null.
When it happens
Trigger: Calling AndroidSchedulers.main().scheduleDirect(null) or scheduleDirect(null, 500, TimeUnit.MILLISECONDS); a Kotlin lambda variable of type Runnable? forwarded to the scheduler; wrapping the scheduler in a delegating class that passes an unchecked parameter; a mocked or generated Runnable that is null (e.g. Mockito stub returning null).
Common situations: Custom schedulers/decorators around AndroidSchedulers that lose the runnable in transit; Kotlin code where a nullable function reference is passed as Runnable; test doubles whose schedule methods receive unstubbed null arguments; copy-pasted scheduling utility code with an uninitialized field used as the runnable.
Related errors
- Scheduler Callable returned null
- scheduler == null
- looper == null
- Expected to be called on the main thread but was {threadName
AI-assisted analysis of ReactiveX/RxAndroid@afaea28046 (2026-08-14).
Data as JSON: /api/errors/1936fc22d6dad8de.
Report an issue: GitHub.