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
- Always pass a concrete unit such as TimeUnit.SECONDS or TimeUnit.MILLISECONDS
- Default null units before the call: unit = unit != null ? unit : TimeUnit.SECONDS
- Fix the config/mapping code that produced a null TimeUnit
- 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
- Default TimeUnit at config-load time, not at call time
- Parse unit strings with TimeUnit.valueOf and reject unknown values early
- Keep timeout+unit as a single Duration-like record
- Add unit==null assertions in wrapper APIs
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
- Task must not be null
- Timeout must be positive
- Timed out waiting for Task
- Must not be invoked on main thread
- deleteAll was set to true but keys were also provided
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/9179bbee25fed6c0.
Report an issue: GitHub.