microg/GmsCore · error · IllegalArgumentException

Callable must not be null

Error message

Callable must not be null

What it means

The deprecated Tasks.call(Executor, Callable) requires a non-null Callable whose body produces the task result. A null Callable has nothing to run on the executor, so the method throws this IllegalArgumentException immediately.

Source

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

     */
    @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() {
        TaskImpl<TResult> task = new TaskImpl<>();

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Pass a real Callable implementation/lambda; if the work is optional, guard the call site instead of passing null
  2. Return an already-completed Task via Tasks.forResult(defaultValue) when there is no work to run
  3. Migrate to TaskCompletionSource (per deprecation notice) and only start work when the callable exists

Example fix

// before
Task<Integer> task = Tasks.call(executor, getCallable()); // getCallable() may return null
// after
Callable<Integer> c = getCallable();
Task<Integer> task = (c != null)
    ? Tasks.call(executor, c)
    : Tasks.forResult(0); // or skip the call entirely
Defensive patterns

Strategy: validation

Validate before calling

if (callable == null) {
    return Tasks.forResult(defaultValue); // nothing to run
}

Type guard

static <T> boolean hasWork(Callable<T> c) {
    return c != null;
}

Try / catch

try {
    Task<T> t = Tasks.call(executor, callable);
} catch (IllegalArgumentException e) {
    // skip or provide default result
    Task<T> t = Tasks.forResult(null);
}

Prevention

When it happens

Trigger: Calling Tasks.call(executor, null), often from a generic helper that forwards a supplier/callable which is null (uninitialized lambda field, optional computation skipped), or reflective/dynamic code that failed to build the Callable.

Common situations: Kotlin/Java interop where a Kotlin lambda coerced to Callable? arrives as null; helper methods like runIfConfigured(executor, maybeCallable) invoked when the computation is absent; refactors that removed the work but left the call site.

Related errors


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