{"record":{"id":"ab831037eb85727d","repo":"microg/GmsCore","slug":"must-not-be-invoked-on-main-thread","errorCode":null,"errorMessage":"Must not be invoked on main thread","messagePattern":"Must not be invoked on main thread","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java","lineNumber":70,"sourceCode":"        if (task.isComplete()) return handleCompletedTask(task);\n        CountDownLatch latch = new CountDownLatch(1);\n        task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown());\n        if (latch.await(timeout, unit)) {\n            return handleCompletedTask(task);\n        }\n        throw new TimeoutException(\"Timed out waiting for Task\");\n    }\n\n    /**\n     * Blocks until the specified Task is complete.\n     *\n     * @return the Task's result\n     * @throws ExecutionException   if the Task fails\n     * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete\n     */\n    public static <TResult> TResult await(Task<TResult> task) throws ExecutionException, InterruptedException {\n        if (Looper.getMainLooper().getThread() == Thread.currentThread())\n            throw new IllegalStateException(\"Must not be invoked on main thread\");\n        if (task == null) throw new IllegalArgumentException(\"Task must not be null\");\n        if (task.isComplete()) return handleCompletedTask(task);\n        CountDownLatch latch = new CountDownLatch(1);\n        task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown());\n        latch.await();\n        return handleCompletedTask(task);\n    }\n\n    private static <TResult> TResult handleCompletedTask(Task<TResult> task) throws ExecutionException {\n        if (task.isSuccessful()) {\n            return task.getResult();\n        }\n        if (task.isCanceled()) {\n            throw new CancellationException(\"Task is already canceled\");\n        }\n        throw new ExecutionException(task.getException());\n    }\n","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java#L52-L88","documentation":"The unbounded Tasks.await(task) overload blocks the calling thread indefinitely, so it refuses to run on the Android main thread: it compares Looper.getMainLooper().getThread() to the current thread and throws IllegalStateException('Must not be invoked on main thread'). Blocking the main thread would freeze the UI and trigger ANRs.","triggerScenarios":"Calling Tasks.await(task) from an Activity/Fragment method, a UI click handler, or any code executing on the main looper.","commonSituations":"Quick demo code written in onCreate; calling await from a ViewModel method invoked on the main thread; forgetting that callbacks like onClick run on the main thread.","solutions":["Move the await call to a background thread (worker thread, executor, or coroutine dispatch to IO)","Use the listener-based APIs (addOnCompleteListener/addOnSuccessListener) instead of blocking await on the main thread","Check Looper.myLooper() == Looper.getMainLooper() in your own wrapper and route to a background executor","Use Tasks.await(task, timeout, unit) off-thread if you need a bounded wait"],"exampleFix":"// before // in onCreate() on main thread\nUser u = Tasks.await(getUserTask());\n// after\nexecutor.execute(() -> {\n    try {\n        User u = Tasks.await(getUserTask());\n    } catch (ExecutionException | InterruptedException e) {\n        // handle\n    }\n});","handlingStrategy":"validation","validationCode":"if (Looper.myLooper() == Looper.getMainLooper()) {\n    throw new IllegalStateException(\"await must run off the main thread\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    T r = Tasks.await(task);\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"main thread\")) { /* re-dispatch to background executor */ }\n} catch (ExecutionException | InterruptedException e) {\n    // normal failure handling\n}","preventionTips":["Never call blocking await() from UI callbacks (onCreate, onClick, onResume)","Route await through a dedicated background executor wrapper","Prefer addOnCompleteListener on the main thread over blocking waits in UI code","Add a debug-mode assertion that fails fast when awaiting on the main looper"],"tags":["tasks","main-thread","android","await","illegal-state"],"backgroundTag":"unsupported-platform","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}