Back to Thread Lifecycle: Priority, Interruption, Factory
Q5Question 5 of 5

Custom ThreadFactory?

Key Concepts
  • ThreadFactory is a single-method interface that an executor calls to create each worker thread.
  • The default factory produces threads named pool-N-thread-M with no uncaught-exception handler.
  • A custom factory controls four things at birth: name prefix, daemon flag, priority, and UncaughtExceptionHandler.
  • Meaningful names make thread dumps and log lines instantly actionable.
  • Daemon threads do not block JVM shutdown; non-daemon threads keep the JVM alive until they finish.
  • UncaughtExceptionHandler catches throwables that escape the run method, preventing silent deaths.
  • Pass the factory to the ThreadPoolExecutor constructor or Executors.newFixedThreadPool(n, factory).

Definition

A custom ThreadFactory names, flags, prioritizes, and attaches a crash handler to every pool thread at birth: pass it to the ThreadPoolExecutor constructor so observability starts before the first task runs.
  • ThreadFactory – single-method interface: Thread newThread(Runnable r).
  • Name – a meaningful prefix like settlement-pool-thread-N for actionable thread dumps.
  • Daemon – setDaemon(true) so the pool does not block JVM shutdown for background work.
  • Priority – setPriority(int) for a weak hint to the scheduler.
  • UncaughtExceptionHandler – catches throwables that escape run(), preventing silent thread deaths.
  • Usage – pass to new ThreadPoolExecutor(core, max, keepAlive, unit, queue, factory) or Executors.newFixedThreadPool(n, factory).

Imagine National Bank of India hires temporary clerks for the settlement counter. The default HR process gives them no badge, no shift rule, and nobody to report accidents to. A custom ThreadFactory is proper onboarding: every clerk gets a badge, is marked as temp or permanent, and is told whom to call if something blows up.

Understanding the Concepts

What is ThreadFactory?

ThreadFactory has exactly one method: Thread newThread(Runnable r). The executor calls this method every time it needs a new worker thread. You build the Thread, configure it, and return it.

How do you build a custom factory with meaningful names?

Keep an AtomicInteger counter and name each thread with a descriptive prefix.

JAVA
package com.bank.pool;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

public class BankThreadFactory implements ThreadFactory {

    private static final Logger LOG = Logger.getLogger(BankThreadFactory.class.getName());
    private final String prefix;
    private final boolean daemon;
    private final AtomicInteger counter = new AtomicInteger(0);

    public BankThreadFactory(String prefix, boolean daemon) {
        this.prefix = prefix;
        this.daemon = daemon;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r, prefix + "-thread-" + counter.incrementAndGet());
        t.setDaemon(daemon);
        t.setPriority(Thread.NORM_PRIORITY);
        t.setUncaughtExceptionHandler((thread, ex) ->
            LOG.log(Level.SEVERE, "Uncaught exception in " + thread.getName(), ex));
        return t;
    }
}

Why do thread names matter?

A thread dump with default names shows pool-1-thread-1, pool-2-thread-3, which is meaningless in a system with dozens of pools. Meaningful names like settlement-pool-thread-3 or audit-pool-thread-1 make thread dumps instantly actionable. You can see which pool is stuck without tracing through stack frames.

How do you set daemon status?

setDaemon(true) for background pools like cache refresh or metrics collection so the JVM can exit cleanly. setDaemon(false) for batch processing jobs that must finish before the JVM exits.

JAVA
// Cache refresh: daemon=true, JVM can exit even if refresh is running
new BankThreadFactory("cache-refresh", true);

// Settlement batch: daemon=false, JVM waits for settlements to complete
new BankThreadFactory("settlement-batch", false);

How does UncaughtExceptionHandler prevent silent deaths?

Exceptions from tasks submitted with submit() are captured in the Future. But exceptions from tasks submitted with execute() or thrown during thread creation propagate to the UncaughtExceptionHandler. Without a handler, the exception prints to stderr and the thread dies silently. The handler guarantees logging.

JAVA
package com.bank.pool;

import java.util.logging.Level;
import java.util.logging.Logger;

public class ExceptionHandlerDemo {
    private static final Logger LOG = Logger.getLogger(ExceptionHandlerDemo.class.getName());

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            throw new RuntimeException("Corrupt trade record");
        }, "settlement-worker-1");

        t.setUncaughtExceptionHandler((thread, ex) -> {
            LOG.log(Level.SEVERE, "Thread " + thread.getName() + " died", ex);
            // Alert operations, increment error metric, etc.
        });

        t.start();
    }
}

How do you pass the factory to the executor?

JAVA
package com.bank.pool;

import java.util.concurrent.*;

public class PoolCreation {
    // Option 1: ThreadPoolExecutor constructor
    private static final ExecutorService pool1 = new ThreadPoolExecutor(
        4, 4, 0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<>(100),
        new BankThreadFactory("settlement-pool", false)
    );

    // Option 2: Executors convenience method
    private static final ExecutorService pool2 = Executors.newFixedThreadPool(4,
        new BankThreadFactory("audit-pool", true));
}

How does Guava's ThreadFactoryBuilder simplify this?

Google Guava provides a fluent builder that handles naming, daemon, priority, and exception handler in one chain.

JAVA
// Guava approach
ThreadFactory factory = new ThreadFactoryBuilder()
    .setNameFormat("settlement-pool-thread-%d")
    .setDaemon(false)
    .setPriority(Thread.NORM_PRIORITY)
    .setUncaughtExceptionHandler((t, e) -> LOG.severe("Error in " + t.getName()))
    .build();

What are common mistakes?

JAVA
// WRONG: default factory, anonymous thread names
ExecutorService pool = Executors.newFixedThreadPool(4);
// Thread dump shows: pool-1-thread-1, pool-1-thread-2 (meaningless)
// Exception dies silently to stderr with no structured logging

// CORRECT: custom factory with meaningful names and exception handler
ExecutorService pool = Executors.newFixedThreadPool(4,
    new BankThreadFactory("settlement-pool", false));
// Thread dump shows: settlement-pool-thread-1, settlement-pool-thread-2
// Exceptions logged with thread name for actionable debugging

Should the counter be static or per-factory-instance?

Per-factory-instance is the standard approach. This way, each pool's threads are numbered independently starting from 1. A static counter would give globally unique numbers across all pools, which is useful for debugging but less intuitive.

What are common interview follow-up questions?

  • How do you name virtual threads? Virtual threads support Thread.ofVirtual().name("prefix-", startId) which provides auto-incrementing names.
  • Should production pools always have a custom ThreadFactory? Yes. Every production pool should have named threads and an UncaughtExceptionHandler. This is a non-negotiable observability baseline.
  • How does Spring Boot's ThreadPoolTaskExecutor handle thread naming? It accepts a threadNamePrefix property that auto-generates sequential names.

Visual Overview

DIAGRAM
  ThreadPoolExecutor
  ┌──────────────────────────────────────────┐
  │                                          │
  │  Needs a new worker thread?              │
  │           │                              │
  │           ▼                              │
  │  ThreadFactory.newThread(runnable)       │
  │           │                              │
  │           ▼                              │
  │  ┌──────────────────────────────┐        │
  │  │ Custom ThreadFactory:        │        │
  │  │  name: settlement-pool-thd-1 │        │
  │  │  daemon: false               │        │
  │  │  priority: NORM (5)          │        │
  │  │  handler: LOG.severe(...)    │        │
  │  └──────────────────────────────┘        │
  │           │                              │
  │           ▼                              │
  │  Worker thread starts with               │
  │  name, daemon, priority, handler set     │
  └──────────────────────────────────────────┘

Key Takeaway

A ThreadFactory is the single-method factory that an executor calls to create each worker thread. The default factory gives anonymous names and no crash handler. I write a custom factory whenever a pool runs in production, controlling name, daemon status, priority, and uncaught-exception handler at the moment of birth.

The most important is the name. A dump full of settlement-pool-thread-3 is instantly actionable. The handler is the safety net for exceptions that escape outside the Future path.

At National Bank of India every production pool uses a BankThreadFactory. The first time a corrupt trade record threw outside the Future, the handler caught it and told us exactly which worker died.

Remember one sentence: a custom ThreadFactory makes observability start before the first task runs, because you name, flag, and protect every worker at the moment of its creation.

---

Share
Use ← → arrow keys to navigate