microg/GmsCore · error · IllegalArgumentException

Executor must not be null

Error message

Executor must not be null

What it means

The deprecated Tasks.call(Executor, Callable) validates its arguments up front. A null Executor cannot schedule the Callable, so the method throws this IllegalArgumentException immediately instead of failing later inside executor.execute().

Source

Thrown at play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java:115

     * @deprecated Use {@link TaskCompletionSource} instead, which allows the caller to manage their own Executor.
     */
    @Deprecated
    public static <TResult> Task<TResult> call(Callable<TResult> callable) {
        return call(TaskExecutors.MAIN_THREAD, callable);
    }

    /**
     * Returns a {@link Task} that will be completed with the result of the specified {@code Callable}.
     * <p/>
     * If a non-{@link Exception} throwable is thrown in the callable, the {@link Task} will be failed with a
     * {@link RuntimeException} whose cause is the original throwable.
     *
     * @param executor the Executor to use to call the {@code Callable}
     * @deprecated Use {@link TaskCompletionSource} instead, which allows the caller to manage their own Executor.
     */
    @Deprecated
    public static <TResult> Task<TResult> call(Executor executor, Callable<TResult> callable) {
        if (executor == null) throw new IllegalArgumentException("Executor must not be null");
        if (callable == null) throw new IllegalArgumentException("Callable must not be null");
        TaskCompletionSource<TResult> taskCompletionSource = new TaskCompletionSource<>();
        executor.execute(() -> {
            try {
                taskCompletionSource.setResult(callable.call());
            } catch (Exception e) {
                taskCompletionSource.trySetException(e);
            } catch (Throwable t) {
                taskCompletionSource.trySetException(new RuntimeException(t));
            }
        });
        return taskCompletionSource.getTask();
    }

    /**
     * Returns a canceled Task.
     */
    public static <TResult> Task<TResult> forCancelled() {

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Pass a real Executor, e.g. Executors.newSingleThreadExecutor(), or TaskExecutors/ContextCompat.getMainExecutor(context)
  2. Keep using the no-executor overload Tasks.call(Callable) only if you intend the deprecated default behavior — better: migrate to TaskCompletionSource as the Javadoc recommends
  3. Audit where the Executor value originates and ensure it is initialized before Tasks.call is invoked

Example fix

// before
Task<String> task = Tasks.call(null, () -> loadData()); // throws
// after
Executor executor = Executors.newSingleThreadExecutor();
Task<String> task = Tasks.call(executor, () -> loadData());
// or preferred modern form:
TaskCompletionSource<String> tcs = new TaskCompletionSource<>();
executor.execute(() -> { try { tcs.setResult(loadData()); } catch (Exception e) { tcs.setException(e); } });
Defensive patterns

Strategy: validation

Validate before calling

if (executor == null) {
    executor = Executors.newSingleThreadExecutor();
}

Type guard

static boolean isRunnableExecutor(Executor e) {
    return e != null;
}

Try / catch

try {
    Task<T> t = Tasks.call(executor, callable);
} catch (IllegalArgumentException e) {
    // fall back to a default executor
    Task<T> t = Tasks.call(Executors.newSingleThreadExecutor(), callable);
}

Prevention

When it happens

Trigger: Calling Tasks.call(null, callable), usually because the Executor came from a variable/config that is null (e.g. a field never initialized, an injected executor not provided, or doInBackground-style code passing null to use 'default' behavior).

Common situations: Migrating code from Tasks.call(Callable) to the executor variant and passing null hoping for a default executor; dependency-injection graph missing an Executor binding; custom Executor created conditionally (e.g. after a permission/setup step) but call() runs earlier.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/57fbd8df03cff2fb. Report an issue: GitHub.