Android
Handler
Threading
Concurrency
Java

Android Why can't I create a handler in new thread

Master System Design with Codemia

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

Introduction

A Handler is not just a callback utility. It is a thin wrapper around a thread's MessageQueue, which means the thread must have an active Looper before a Handler can be created.

That is why code that works on the main thread often crashes on a plain background thread with the message about calling a Handler on a thread that has not called Looper.prepare().

How Handler, Looper, and MessageQueue Fit Together

Android threads do not automatically process queued messages. The main thread is special because the framework starts a Looper for it during app startup. A plain Thread does not get that setup.

The relationship looks like this:

  • A thread may own one Looper.
  • The Looper owns one MessageQueue.
  • A Handler posts work into that queue.

If the queue does not exist, the Handler constructor has nowhere to send messages. That is why this code fails:

java
1Thread worker = new Thread(() -> {
2    Handler handler = new Handler();
3    handler.post(() -> System.out.println("Will never run"));
4});
5
6worker.start();

On older Android APIs this typically throws an exception saying the thread has not called Looper.prepare().

Creating a Handler Correctly on a Background Thread

If you truly need a background thread with its own queue, you must prepare a Looper on that thread and then start looping. The low-level version looks like this:

java
1Thread worker = new Thread(() -> {
2    Looper.prepare();
3
4    Handler handler = new Handler(Looper.myLooper());
5    handler.post(() -> {
6        Log.d("Worker", "Running on " + Thread.currentThread().getName());
7    });
8
9    Looper.loop();
10});
11
12worker.start();

This works, but it has an important cost: Looper.loop() blocks forever until the looper is quit. That means you now have to manage thread shutdown yourself.

For most Android code, HandlerThread is the better option because it bundles the correct setup:

java
1HandlerThread handlerThread = new HandlerThread("image-loader");
2handlerThread.start();
3
4Handler backgroundHandler = new Handler(handlerThread.getLooper());
5backgroundHandler.post(() -> {
6    Log.d("Worker", "Background work started");
7});
8
9// Later, when you are done with the thread:
10handlerThread.quitSafely();

HandlerThread creates a worker thread, prepares the Looper, and exposes it with getLooper(). That removes the fragile manual setup.

Why Explicit Loopers Are Better

Older Android code often used new Handler() with no arguments. That constructor binds to the current thread's Looper, which is convenient but also easy to misuse. On a background thread with no looper, it fails. On the main thread, it works, but the call site does not make the target thread obvious.

Being explicit is clearer:

java
1Handler mainHandler = new Handler(Looper.getMainLooper());
2mainHandler.post(() -> {
3    textView.setText("Updated on the UI thread");
4});

This style makes the threading intention visible in the code and avoids relying on whichever thread happens to be executing the constructor.

Posting Back to the Main Thread

Another source of confusion is that many developers do not actually need a background Handler. They only need to send results back to the UI thread after doing work elsewhere.

In that case, do the heavy work on a background executor and post the UI update to Looper.getMainLooper():

java
1ExecutorService executor = Executors.newSingleThreadExecutor();
2Handler mainHandler = new Handler(Looper.getMainLooper());
3
4executor.execute(() -> {
5    String result = "Loaded data";
6
7    mainHandler.post(() -> {
8        textView.setText(result);
9    });
10});

This pattern is common because Android views must be touched from the main thread.

When to Use Newer APIs Instead

Handler is still valid, but it is not the best abstraction for every concurrency problem. If the job must continue even after the app process is backgrounded, WorkManager is usually more appropriate. If you are writing Kotlin, coroutines give you clearer structure for switching between background and UI work.

For example, in Kotlin with coroutines:

kotlin
1lifecycleScope.launch {
2    val result = withContext(Dispatchers.IO) {
3        repository.loadUser()
4    }
5
6    textView.text = result.name
7}

This does not replace every Handler, but it does replace a lot of the boilerplate that older Android apps used.

Common Pitfalls

The first pitfall is assuming every thread has a Looper. Only the main thread gets one automatically. Other threads need explicit setup or a helper like HandlerThread.

Another issue is forgetting to stop a HandlerThread. If you never call quit() or quitSafely(), the looper keeps waiting for work and the thread stays alive longer than intended.

It is also common to create a background Handler when only a main-thread Handler is needed. If the real goal is updating the UI after background work, keep the queue on the main thread and use an executor or coroutine for the heavy task.

Finally, do not use a Handler as a replacement for all long-running background work. For scheduled, deferrable, or guaranteed jobs, Android background APIs such as WorkManager are more robust.

Summary

  • A Handler needs a thread that already has a Looper and MessageQueue.
  • The main thread has a Looper by default, but a plain Thread does not.
  • Use HandlerThread when you need a background thread with its own message loop.
  • Use Looper.getMainLooper() when posting results back to the UI thread.
  • For many modern Android tasks, executors, coroutines, or WorkManager are simpler than managing a custom Handler thread.

Course illustration
Course illustration

All Rights Reserved.