microg/GmsCore · error · IllegalArgumentException

TimeUnit must not be null

Error message

TimeUnit must not be null

What it means

Guard inside the timed overload of Tasks.await(task, timeout, unit). The caller passed a null TimeUnit, so the timeout duration cannot be interpreted (nanoseconds vs seconds are indistinguishable). Rather than silently treating null as some default unit, the method validates its argument and throws to force the caller to name the time unit explicitly.

Source

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

/**
 * {@link Task} utility methods.
 */
@PublicApi
public final class Tasks {

    /**
     * Blocks until the specified Task is complete.
     *
     * @return the Task's result
     * @throws ExecutionException   if the Task fails
     * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete
     * @throws TimeoutException     if the specified timeout is reached before the Task completes
     */
    public static <TResult> TResult await(Task<TResult> task, long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException {
        if (task == null) throw new IllegalArgumentException("Task must not be null");
        if (timeout <= 0) throw new IllegalArgumentException("Timeout must be positive");
        if (unit == null) throw new IllegalArgumentException("TimeUnit must not be null");
        if (task.isComplete()) return handleCompletedTask(task);
        CountDownLatch latch = new CountDownLatch(1);
        task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown());
        if (latch.await(timeout, unit)) {
            return handleCompletedTask(task);
        }
        throw new TimeoutException("Timed out waiting for Task");
    }

    /**
     * Blocks until the specified Task is complete.
     *
     * @return the Task's result
     * @throws ExecutionException   if the Task fails
     * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete
     */
    public static <TResult> TResult await(Task<TResult> task) throws ExecutionException, InterruptedException {
        if (Looper.getMainLooper().getThread() == Thread.currentThread())

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Always pass a concrete unit such as TimeUnit.SECONDS or TimeUnit.MILLISECONDS
  2. Default null units before the call: unit = unit != null ? unit : TimeUnit.SECONDS
  3. Fix the config/mapping code that produced a null TimeUnit
  4. Validate unit alongside timeout in your own wrapper method

Example fix

// before
T r = Tasks.await(task, config.timeout, config.unit); // unit may be null
// after
TimeUnit unit = config.unit != null ? config.unit : TimeUnit.SECONDS;
T r = Tasks.await(task, config.timeout, unit);
Defensive patterns

Strategy: validation

Validate before calling

if (unit == null) unit = TimeUnit.SECONDS; // or reject explicitly

Type guard

boolean hasUnit(TimeUnit u) { return u != null; }

Try / catch

try {
    T r = Tasks.await(task, timeout, unit);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("TimeUnit must not be null")) { /* supply default unit */ }
}

Prevention

When it happens

Trigger: Calling Tasks.await(task, 5, null) — commonly when the unit comes from an uninitialized field, a config lookup that returned null, or an optional parameter defaulted to null.

Common situations: Config-driven timeouts where the unit string failed to map to a TimeUnit; default-parameter mistakes; reflection-built call sites.

Related errors


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