Android
Looper
Threading
Handler
Error Fix

Can't create handler inside thread that has not called Looper.prepare

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

This Android exception means code tried to create a Handler on a thread that has no Looper. The main thread already has one, but ordinary background threads do not, so the correct fix depends on whether you want to update the UI or whether you truly need a background message queue.

Why The Exception Happens

The simplest failing pattern looks like this:

kotlin
1import android.os.Handler
2
3Thread {
4    val handler = Handler()
5}.start()

That thread has no prepared message loop, so Android throws:

text
Can't create handler inside thread that has not called Looper.prepare()

A Handler is tied to a message queue, and the queue is owned by a Looper. No looper means no handler target.

The Most Common Fix: Post Back To The Main Thread

Usually the code does not need a new background looper. It just wants to update the UI after work finishes. In that case, post to the main looper explicitly:

kotlin
1import android.os.Handler
2import android.os.Looper
3
4val mainHandler = Handler(Looper.getMainLooper())
5
6Thread {
7    val result = "Loaded"
8    mainHandler.post {
9        println(result)
10    }
11}.start()

In an activity, runOnUiThread is another simple option:

kotlin
1Thread {
2    val result = "Done"
3    runOnUiThread {
4        println(result)
5    }
6}.start()

If the goal is only "switch back to the UI thread," this is the right solution.

Use HandlerThread For A Real Background Queue

If you actually need queued work on a background thread, use HandlerThread:

kotlin
1import android.os.Handler
2import android.os.HandlerThread
3
4val workerThread = HandlerThread("Worker")
5workerThread.start()
6
7val workerHandler = Handler(workerThread.looper)
8workerHandler.post {
9    println("Running on worker looper")
10}

When finished:

kotlin
workerThread.quitSafely()

This is much safer than manually preparing and looping a looper yourself.

Why Looper.prepare() Is Usually The Wrong Fix

You may see code like this:

kotlin
1import android.os.Handler
2import android.os.Looper
3
4Thread {
5    Looper.prepare()
6    val handler = Handler()
7    Looper.loop()
8}.start()

This can work, but it creates a looper lifecycle you now have to own. The thread will sit in Looper.loop() until you shut it down properly.

For most app code, that is unnecessary complexity. HandlerThread, coroutines, or explicit main-thread posting are usually better.

Modern Android Often Uses Coroutines Instead

A modern equivalent with coroutines is often clearer:

kotlin
1lifecycleScope.launch {
2    val result = withContext(Dispatchers.IO) {
3        "Loaded"
4    }
5
6    println(result)
7}

This avoids manual looper management entirely while still respecting Android thread boundaries.

If your codebase already uses coroutines, that is often the most maintainable answer.

The same reasoning applies to callback-based libraries. If a background callback needs to touch a TextView, adapter, or fragment state, switch back to the main thread first rather than creating a handler on the callback thread. The exception is often a sign that the threading model was never made explicit in the first place.

Common Pitfalls

One common mistake is creating a bare Handler() inside a background thread when the real requirement is only to update the UI later.

Another issue is treating Looper.prepare() as a quick fix without planning thread shutdown or ownership.

A third problem is mixing old handler patterns and coroutine patterns in the same workflow without a clear threading model.

Finally, developers sometimes forget that many Android APIs must still be called on the main thread even if background work produced the data successfully.

Summary

  • A Handler needs a Looper, and normal worker threads do not have one automatically.
  • For UI updates, post to Looper.getMainLooper() or use runOnUiThread.
  • For a real background message queue, use HandlerThread.
  • Avoid manual Looper.prepare() unless you truly need low-level control.
  • In coroutine-based apps, switching dispatchers is often cleaner than creating handlers directly.
  • Keep background work and UI callbacks separate so threading bugs stay obvious.

Course illustration
Course illustration

All Rights Reserved.