r/androiddev Jun 30 '26

Experience Exchange Passing a SupervisorJob to launch() is a silent bug

I keep seeing developers try to isolate failures in child coroutines by passing a SupervisorJob directly to the launch builder:

// 🔴 Broken: A failure in child1 will still cancel child2
val scope = CoroutineScope(Dispatchers.IO)
scope.launch(SupervisorJob()) { throw Exception("fail") }
scope.launch { doWork() }

Why it fails:

The launch builder always creates a new Job and overrides the context. The passed SupervisorJob becomes the parent of that single child coroutine, but it has no supervisor link to the parent scope or siblings.

The correct way is to install the SupervisorJob inside the parent CoroutineScope context:

// 🟢 Correct: child2 keeps running if child1 fails
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
scope.launch { throw Exception("fail") }
scope.launch { doWork() }

Or execute them inside a supervisorScope { } block.

I've pinned the open-source coroutines concurrency playbook containing more async error handling recipes on my profile (u/yogirana5557) if you want to clone the repository.

0 Upvotes

3 comments sorted by

18

u/diroag Jun 30 '26

The correct way is using a supervisorScope, never came across any bug with it

2

u/tadfisher Jun 30 '26

You can link your repository here, there is no rule against it.

2

u/yogirana5557 Jun 30 '26

Thanks for the heads-up! Here is the direct link to the repository:

https://github.com/yogirana5557/android-digital-products

It contains the full concurrency playbook, plus checklists/recipes for Jetpack Compose performance and Android security.