Email copiado — support@tuurt.com
Cargando experiencia
kotlin · October 08, 2026 · 6 min

WorkManager on Android: Why a Deferred Task Does Not Run in Doze Mode

WorkManager does not promise to run a task when we ask for it, only within the rules Doze and App Standby impose on the system. We look at how that negotiation works and what combination of causes delayed an hourly sync to every three or four hours.

By Tuurt Team

WorkManager on Android: why a deferred task doesn't run when the system enters Doze

WorkManager is presented as the recommended way to schedule background work on Android: syncing data, uploading a pending file, clearing a cache. The implicit promise is that, as long as the device meets the constraints we declared, the system will run that task at some reasonable point. What that promise doesn't say as clearly is that "some reasonable point" can mean minutes, or it can mean hours, depending on how still the device has been. The first time a team sees a task with a fifteen-minute setInitialDelay actually run two hours later, the usual reaction is to assume WorkManager has a bug. It doesn't: it's following exactly the rules Doze imposes on it, and those rules aren't documented in WorkManager's own docs but in the system's power management documentation.

What Doze actually restricts

Doze is a power-saving mode the system activates when the device has been still for a while, with the screen off and not connected to a charger. It doesn't take long: within the first few minutes the system already starts applying light restrictions, and if that stillness holds, it moves into full Doze with much more aggressive restrictions on network access, wakelocks, syncs, and, what matters here, job and alarm execution.

Inside full Doze, the system periodically opens short maintenance windows in which it lifts the restrictions, lets pending jobs run, makes the network available again, and allows syncs to catch up. Between one window and the next, the interval grows: the first windows are relatively close together, but the longer the device stays still, the further apart they get spaced. A task that became ready to run right after a maintenance window ended doesn't run then — it waits for the next one, and that next one can be considerably further out than the original setInitialDelay suggested.

App Standby Buckets: a second restriction, independent of Doze

Doze depends on the device's physical state — still, screen off, unplugged — and affects every app equally while that state holds. App Standby is a different mechanism: it classifies each app into a usage bucket — active, working set, frequent, rare, or restricted — based on how and how often the person uses it, and it applies regardless of whether the device is in Doze at that moment. An app the person hasn't opened in several days falls into a more restrictive bucket, and the system limits how many times a day that app can run background jobs or access the network, with no direct relation to whether the phone is idle or in active use right then.

This matters because both mechanisms apply at once. A task can be blocked by Doze, by the app's standby bucket, or by both combined. Diagnosing "why didn't it run" means checking both, not just one.

How WorkManager negotiates those restrictions

WorkManager isn't the execution mechanism itself: it's a layer that picks, depending on the Android version available, which underlying system mechanism to use, and on the vast majority of devices today that mechanism is JobScheduler. That choice is what explains the behavior, because JobScheduler was designed from the start to be Doze-aware: a job scheduled through JobScheduler is automatically deferred to the next maintenance window if the system is in full Doze when that job would otherwise run. Nothing special needs to be declared to get that behavior; it's the default.

This is, underneath it, a negotiation in the right direction: WorkManager doesn't promise to run the task at the exact moment we asked for, it promises to run it while staying within the system's rules, which keeps the app from fighting the power manager and ending up on a list of apps flagged for aggressive battery use. The cost of that negotiation is that any initialDelay, any PeriodicWorkRequest interval, is a minimum, not a guarantee: the work won't run before that time, but it can run considerably later if it lands during a stretch of deep Doze.

For work that genuinely needs lower latency, WorkManager 2.7 introduced expedited work (setExpedited), which on Android 12 and above uses JobScheduler's expedited jobs, running with higher priority and less exposure to maintenance windows, within a daily quota the system assigns based on the app's standby bucket. On earlier versions, WorkManager simulates that behavior with a short-lived foreground service. Expedited work isn't a way to bypass Doze entirely — it's a way to ask for priority within a budget the system still controls.

Why going straight to AlarmManager doesn't fix the root problem

A common reaction to seeing a task deferred too long is to drop WorkManager and schedule the alarm directly with AlarmManager, using setExactAndAllowWhileIdle to force it to run even inside Doze. It works, but with a restriction that's usually discovered late: the system rate-limits exact alarms of this kind to roughly one every nine minutes per app during Doze. If the use case needs several runs spaced closer together than that, most of them will still get deferred — the only difference is that now it's a mechanism we have to maintain ourselves, instead of one the system already handles for us. AlarmManager solves the symptom for a specific, isolated case — a single alarm that genuinely needs to fire at a precise moment, like a reminder — but it isn't a general replacement for WorkManager when it comes to recurring background work.

What we found reviewing a real delay case

Reviewing a PeriodicWorkRequest meant to sync data every hour that, in practice, ran every three or four hours whenever the device sat still overnight, the cause wasn't a single thing. A one-hour interval already sits uneasily with deep Doze, since overnight maintenance windows can end up spaced considerably further apart than that. On top of that, a network constraint (setRequiredNetworkType(NetworkType.UNMETERED)) combined with the test phone's wifi being set to turn off with the screen meant the task lacked the network it required even during the maintenance windows where the system did try to run it: the job got marked ready, the network constraint wasn't met, and it went back to waiting for the next window. Neither cause on its own explained the full delay; together, they did.

What we check before assuming a deferred task will run on time

Before signing off on a background work schedule, we confirm which mechanism WorkManager uses underneath on the Android versions the project supports, since Doze behavior isn't identical across all of them; we review whether the declared constraints — network type, charging state, storage — are realistic to satisfy during an overnight maintenance window and not just during active device use; we separate which work genuinely tolerates a delay of hours from which needs expedited work or, in specific, isolated cases, an exact alarm with its rate limit accounted for; and we test the actual behavior with the device in prolonged idle using adb shell dumpsys deviceidle force-idle, rather than trusting that behavior on an emulator with the screen on reflects what will happen on a phone someone leaves untouched overnight.

android workmanager doze background-work
← Back to blog