Looper thread
Android development
threading
messaging
Java programming

How to create a Looper thread, then send it a message immediately?

Master System Design with Codemia

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

Introduction

Creating a Looper thread and sending it a message immediately is really about one timing constraint: the Handler cannot safely accept messages until the thread has prepared its Looper. If you post too early, you either get a NullPointerException, a handler without a prepared looper, or a race where the background thread is not ready yet.

The Core Problem

A Looper thread is usually created like this:

  1. start a new thread
  2. call Looper.prepare()
  3. create a Handler
  4. call Looper.loop()

The difficulty is that the main thread may try to send a message before step 3 finishes.

That means the solution is not "send faster." The solution is to expose the handler only after initialization is complete.

The Simplest Correct Tool: HandlerThread

Android already provides HandlerThread, which solves exactly this setup problem.

java
1HandlerThread workerThread = new HandlerThread("WorkerThread");
2workerThread.start();
3
4Handler workerHandler = new Handler(workerThread.getLooper(), msg -> {
5    System.out.println("Received: " + msg.what);
6    return true;
7});
8
9Message message = workerHandler.obtainMessage(1);
10workerHandler.sendMessage(message);

getLooper() blocks until the looper is ready, so this is the easiest safe way to create a looper-backed worker and send a message right away.

Manual Looper Thread Initialization

If you need to build the thread manually, you must coordinate readiness. One way is using CountDownLatch:

java
1import android.os.Handler;
2import android.os.Looper;
3import android.os.Message;
4import java.util.concurrent.CountDownLatch;
5
6public class LooperWorker extends Thread {
7    private Handler handler;
8    private final CountDownLatch ready = new CountDownLatch(1);
9
10    @Override
11    public void run() {
12        Looper.prepare();
13
14        handler = new Handler(msg -> {
15            System.out.println("Handled message: " + msg.what);
16            return true;
17        });
18
19        ready.countDown();
20        Looper.loop();
21    }
22
23    public Handler getHandler() throws InterruptedException {
24        ready.await();
25        return handler;
26    }
27}

Usage:

java
1LooperWorker worker = new LooperWorker();
2worker.start();
3
4Handler handler = worker.getHandler();
5handler.sendMessage(Message.obtain(handler, 42));

This works because getHandler() waits until the handler exists.

Why Direct Construction Often Fails

A common bad pattern looks like this:

java
LooperWorker worker = new LooperWorker();
worker.start();
worker.handler.sendEmptyMessage(1);

This fails because start() only schedules the thread. It does not guarantee that run() has already reached Looper.prepare() and created the handler by the next line.

This is a classic cross-thread initialization race.

Cleaning Up the Thread

A looper thread should also be stopped cleanly when the work is done:

java
workerThread.quitSafely();

Or, for the manual version, call getLooper().quitSafely() from a controlled path. If you forget shutdown, the thread can stay alive and leak resources.

Choosing Between HandlerThread and Alternatives

Use HandlerThread when:

  • you need one serial background queue
  • you want message-based work
  • legacy Android APIs already use handlers

For newer app code, coroutines, executors, or WorkManager are often easier to reason about. But if the requirement is specifically "looper thread plus messages," HandlerThread is still the clean answer.

Common Pitfalls

The main mistake is sending to the handler before it exists. Thread startup is asynchronous, so "I just started the thread" is not the same as "the looper is ready."

Another mistake is creating a Handler on a thread that never called Looper.prepare(). That triggers runtime errors because the handler has no message queue to bind to.

People also forget shutdown. A looper thread runs until it is told to quit, which can create leaks in long-lived components.

Finally, do not use sleep() to "wait for initialization." It is unreliable and unnecessary when proper coordination tools already exist.

Summary

  • You can only send messages after the looper thread has prepared its Looper and created its Handler.
  • 'HandlerThread is the simplest safe way to do this in Android.'
  • Manual looper threads need a readiness signal such as CountDownLatch.
  • Sending immediately after start() without synchronization is a race.
  • Always shut down looper threads cleanly when they are no longer needed.

Course illustration
Course illustration

All Rights Reserved.