free geoip
37

Fixing “Job was cancelled” Error in Kotlin Coroutine

When working with Kotlin coroutines, one of the most common issues developers face is the “Job was cancelled” error.This error…

When working with Kotlin coroutines, one of the most common issues developers face is the “Job was cancelled” error.
This error can occur unexpectedly during coroutine execution and often confuses beginners who are just learning about coroutine lifecycle and structured concurrency.
In this article, we will explore why this error happens, what it means, and how to handle it effectively in real-world applications.

Understanding the “Job was cancelled” Error

A coroutine in Kotlin is tied to a Job, which represents its lifecycle.
When a coroutine is launched, the job can be in various states: Active, Completed, or Cancelled.
If a coroutine gets cancelled before completing its work, Kotlin throws a CancellationException with the message “Job was cancelled”.

Fixing "Job was cancelled" Error in Kotlin Coroutine

This behavior is by design, as coroutine cancellation is a cooperative mechanism.
It means that when the parent coroutine or scope is cancelled, all child coroutines are also cancelled.
For example, if you launch coroutines in an Activity scope in Android, once the activity is destroyed, the coroutine jobs are automatically cancelled.

Common Scenarios That Trigger the Error

  • Parent scope cancelled: If the parent coroutine scope is cancelled, all child coroutines throw “Job was cancelled”.
  • Timeout exceeded: When using withTimeout or withTimeoutOrNull, coroutines will be cancelled after the time limit.
  • Manual cancellation: If you explicitly call job.cancel() on a coroutine job.
  • Activity/Fragment lifecycle in Android: When the lifecycle owner is destroyed, associated coroutine scopes cancel their jobs.

Example of the Error

Let’s see a simple case where the error appears:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(10) { i ->
            println("Processing $i ...")
            delay(500)
        }
    }

    delay(1200)
    println("Cancelling job...")
    job.cancel()
    job.join()
    println("Job cancelled")
}

In this example, the coroutine starts repeating a task, but after 1200ms we manually cancel it.
The coroutine will throw a CancellationException internally, and you will see the message “Job was cancelled” in logs.

How to Handle the Error

In most cases, you don’t need to catch the CancellationException because it is a normal part of coroutine cancellation.
However, if you want to perform cleanup or release resources, you should handle it properly.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(10) { i ->
                println("Downloading chunk $i ...")
                delay(500)
            }
        } catch (e: CancellationException) {
            println("Job cancelled due to: ${e.message}")
        } finally {
            println("Releasing resources...")
        }
    }

    delay(1200)
    println("Cancelling download...")
    job.cancel(CancellationException("Network issue"))
    job.join()
    println("Coroutine finished")
}

Here we use a try-catch-finally block.
The coroutine will still be cancelled, but before termination, it executes the finally block, allowing you to free up resources such as closing a file, releasing a database connection, or stopping an ongoing network request.

Best Practices to Avoid Issues

  • Always consider lifecycle: In Android, use lifecycleScope or viewModelScope to automatically cancel jobs when the lifecycle ends.
  • Use structured concurrency: Launch coroutines within a proper scope instead of using GlobalScope.
  • Handle timeouts carefully: When using withTimeout, provide fallback mechanisms.
  • Cleanup resources: Always release resources inside finally when a coroutine is cancelled.

Comparison: Normal Exception vs CancellationException

AspectNormal ExceptionCancellationException
CauseUnexpected error (e.g., NullPointerException)Intentional cancellation of coroutine job
Default HandlingCrashes coroutine unless caughtConsidered normal and propagated silently
Need to Handle?Yes, must be caughtNo, unless cleanup is required

Real-World Example in Android

Suppose you are making a network request inside an Android ViewModel.
If the user navigates away, the coroutine should be cancelled. Without proper handling, you may encounter “Job was cancelled” in logs.

class MyViewModel : ViewModel() {

    fun fetchData() {
        viewModelScope.launch {
            try {
                val data = simulateNetworkRequest()
                println("Received: $data")
            } catch (e: CancellationException) {
                println("Coroutine cancelled: ${e.message}")
            }
        }
    }

    private suspend fun simulateNetworkRequest(): String {
        delay(5000) // Simulating long network call
        return "Server Response"
    }
}

In this case, if the user leaves the screen before 5 seconds, the coroutine will be cancelled automatically by viewModelScope.
This prevents memory leaks and ensures efficient resource management.

Conclusion

The “Job was cancelled” error in Kotlin coroutines is not actually an error but a signal that a coroutine was cancelled as expected.
By understanding how coroutine jobs work, why they are cancelled, and how to properly handle cancellation with try-catch-finally, you can build stable and robust applications.
Always use structured concurrency, respect lifecycle scopes, and ensure cleanup in finally blocks for better coroutine management.

rysasahrial

Leave a Reply

Your email address will not be published. Required fields are marked *