TL;DR — Android 14 broke our background work in 4 specific ways:
- Foreground services now require a declared
foregroundServiceTypein the manifest AND a matching argument tostartForeground— miss either and you ship aSecurityExceptionthat fires only on some devices.BOOT_COMPLETEDno longer auto-wakes yourWorkManagerscheduler until the user opens the app once after boot.- Exact alarms (
setExact,setAlarmClock) need the newSCHEDULE_EXACT_ALARMpermission — granted via system Settings, no runtime prompt.- High-priority FCM pushes are throttled for users in the "restricted" / "rarely used" App Standby buckets, regardless of message priority.
| API level | Released | Target SDK | Affects |
|---|---|---|---|
| 34 (Android 14) | August 2023 | targetSdk = 34 | Foreground services — must declare foregroundServiceType in manifest + pass to startForeground |
| 34 (Android 14) | August 2023 | targetSdk = 34 | BOOT_COMPLETED — broadcast delayed until first app open after reboot |
| 34 (Android 14) | August 2023 | targetSdk = 34 | Exact alarms — SCHEDULE_EXACT_ALARM permission required, granted via Settings |
| 34 (Android 14) | August 2023 | targetSdk = 34 | FCM high-priority — throttled in "restricted" / "rarely used" Standby buckets |
When Android 14 (API 34) shipped, Google tightened a cluster of background-work APIs at once. Each change was documented, but the cumulative effect on a real enterprise app was absurd: push notifications stopped arriving for ~15% of users, WorkManager jobs started missing SLAs, and our foreground-service-backed GPS tracker crashed with ForegroundServiceStartNotAllowedException on first cold start.
None of it was a bug. All of it was us not reading the changelog carefully enough. Here's what I wish someone had handed me in one place.
1. Foreground services now require a declared foregroundServiceType
In previous Android versions, any service could startForeground() with a notification. In 14, you MUST declare the type in the manifest AND pass it to startForeground. Types are constrained to a specific list: camera, connectedDevice, dataSync, health, location, mediaPlayback, mediaProjection, microphone, phoneCall, remoteMessaging, shortService, specialUse, systemExempted.
class SyncService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Syncing data")
.setSmallIcon(R.drawable.ic_sync)
.build()
// API 34+: must pass the type you declared in the manifest.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
)
} else {
startForeground(NOTIFICATION_ID, notification)
}
return START_STICKY
}
}And in your manifest:
<service
android:name=".SyncService"
android:foregroundServiceType="dataSync"
android:exported="false" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />If the permission or the type is missing, you'll get a SecurityException at runtime. Sometimes. On some devices. Not always. That's the worst part.
2. BOOT_COMPLETED no longer auto-wakes your scheduler
Android 14 delays broadcast delivery for apps that haven't been opened since boot. If you scheduled recurring work via WorkManager and expected the service to restart on boot, you'll find it doesn't until the user explicitly opens the app.
The fix: declare your receiver in the manifest (not just dynamically), and make sure the app has the high-priority permission. Or — better — design your sync to tolerate lazy re-initialization the first time the user opens the app.
3. Exact alarms require a new permission
If you used AlarmManager.setExact() or setAlarmClock() for a calendar-style reminder, you now need SCHEDULE_EXACT_ALARM declared in the manifest AND the user has to grant it manually via a system settings page. There's no runtime prompt.
Most apps don't need exact alarms. Check honestly: if your reminder can fire within a 10-minute window, switch to setAndAllowWhileIdle() and drop the permission entirely.
4. FCM delivery is doze-sensitive again
Push notifications via Firebase Cloud Messaging are nominally high-priority, but Android 14 throttles high-priority messages more aggressively for apps in the "restricted" or "rarely used" App Standby buckets. Symptoms: some users report "no pushes" while most receive them fine. What's actually happening is FCM is holding their delivery until the next sync window.
Two mitigations:
- Ensure your server sends
priority: "high"on genuine user-actionable pushes (chat, security alerts). Don't use high-priority for promotional content — Android will downgrade you. - When a user repeatedly misses pushes, ask them to exclude your app from battery optimization. Yes, this is a terrible UX. Yes, it's the only thing that works today.
Debugging checklist
When a device reports "no background work running":
# Dump battery / power / standby state for your package
adb shell dumpsys battery
adb shell dumpsys deviceidle
adb shell dumpsys usagestats | grep <your.package.name>
# Force the device into doze mode for repro
adb shell dumpsys deviceidle force-idle
# Inspect which WorkManager jobs are pending
adb shell dumpsys jobscheduler | grep <your.package.name>If dumpsys deviceidle shows your app as =BATTERY_SAVER= or =RESTRICTED=, your WorkManager jobs will be delayed indefinitely. The only fix is user-initiated: they need to whitelist your app in Settings → Battery → Battery optimization.
That's the grim reality of shipping long-running work on Android in 2026. The OS is right to crack down on it. Our job is to design around it — not fight it.