
If you landed here from an old tutorial, the short version is this: do not reach for IntentService or JobIntentService in 2026. Both are deprecated. For almost all deferrable background work on Android today, the answer is WorkManager. If the work has to run right now and keep going after the user leaves the app, use a foreground service. Everything else usually belongs in a coroutine.
I first wrote this post in 2020 recommending JobIntentService, and that advice has aged out. So I have rewritten it for how Android background work actually looks now, including a step-by-step migration off IntentService.
Why IntentService and JobIntentService are both deprecated
IntentService shipped back in API 3. It gave you a simple way to run work off the main thread: you handed it an Intent, it queued the work, ran onHandleIntent on a background thread, and stopped itself when the queue drained. For years it was the default choice, and it was genuinely convenient.
Then Android Oreo (API 26) introduced background execution limits to stop apps draining the battery with unbounded background services. A plain background service can no longer start freely while your app is in the background, which quietly broke the IntentService model. Google marked IntentService deprecated in API 30 (Android 11).
The first thing most people reached for was JobIntentService: it behaved like IntentService on older devices and used JobScheduler on API 26 and above. It was a useful bridge, and it is exactly what I recommended in the original version of this post. But JobIntentService was itself deprecated in androidx.core 1.3.0. It was always meant to be a migration aid, not a destination.
So both classes are dead ends for new code, and WorkManager is where that work belongs now.
What should you use instead in 2026?
Pick based on what the work actually needs:
- Deferrable, guaranteed work (uploads, syncs, sending logs, processing a download): use WorkManager. It selects the right scheduler under the hood, survives app restarts and reboots, honours constraints like “only on unmetered network” or “only while charging”, and works back to API 14. This covers most of what people used
IntentServicefor. - Work that must run immediately and continue while the app is backgrounded (an active upload with a progress notification, audio, location): use a foreground service, or WorkManager expedited work with a foreground notification.
- Simple async work tied to a screen (a network call for the current UI): you probably do not need a service at all. Use a Kotlin coroutine in your
viewModelScope.
For the classic IntentService case, WorkManager is the direct replacement.
How to migrate IntentService to WorkManager
Here is the migration on a real example: an IntentService that uploads a file.
- Add the WorkManager dependency in your module
build.gradle.kts. Check for the latest stable version when you add it.
implementation("androidx.work:work-runtime-ktx:2.10.0")
- Turn the
IntentServiceinto aCoroutineWorker. The oldonHandleIntentbody becomesdoWork. BecausedoWorkis a suspend function, you get structured concurrency for free and never touch a thread by hand.
class UploadWorker(
context: Context,
params: WorkerParameters,
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val fileUri = inputData.getString(KEY_FILE_URI) ?: return Result.failure()
return try {
uploadFile(fileUri)
Result.success()
} catch (e: IOException) {
Result.retry()
}
}
companion object {
const val KEY_FILE_URI = "file_uri"
}
}
- Replace
context.startService(intent)with an enqueuedWorkRequest. Whatever you used to pack into theIntentextras now goes intoinputData.
val request = OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(workDataOf(UploadWorker.KEY_FILE_URI to fileUri))
.build()
WorkManager.getInstance(context).enqueue(request)
- Delete the
<service>entry fromAndroidManifest.xml. WorkManager registers its own component through the app-startup provider, so the old service tag and theBIND_JOB_SERVICEpermission both go away. One less thing to maintain.
That is the whole migration. You drop the manual thread management, the manifest boilerplate, and the API-level branching, and you pick up retries, constraints, and reboot persistence.
If you need the work to start as soon as possible rather than when the system feels like it, mark it expedited:
val request = OneTimeWorkRequestBuilder<UploadWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
Frequently Asked Questions
Is IntentService removed, or just deprecated? It is deprecated, not removed, so old code still compiles and runs. But it is subject to the background execution limits from API 26, so it will not reliably start from the background on modern devices. Treat it as something to migrate away from, not something to keep building on.
Do I still need JobIntentService for devices below API 26?
No. WorkManager already supports API 14 and up and chooses the right scheduler internally, so there is nothing left for JobIntentService to solve. Backwards compatibility was its one reason to exist, and WorkManager covers it.
Can WorkManager run work immediately?
By default WorkManager optimises for battery, so work can be delayed. If you need it to start right away, use expedited work with setExpedited, or use a foreground service for work that must run continuously while the user can see it.
What replaces onHandleIntent?
doWork inside a Worker or CoroutineWorker. With CoroutineWorker it is a suspend function, so you write normal sequential Kotlin and let structured concurrency handle threading and cancellation for you.
Happy coding.
David Cruz davthecoder.com
Loading comments…