DecimalFormat.formatdouble in different threads
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
DecimalFormat is convenient for rendering numbers, but a shared instance is not safe to use from multiple threads. If several requests call format on the same formatter concurrently, you can get corrupted strings or intermittent failures that are very hard to reproduce.
Why the Shared Formatter Pattern Breaks
DecimalFormat keeps mutable internal state while converting numbers to text. That internal reuse is efficient for one thread, but dangerous when multiple threads interleave operations on the same instance.
This pattern looks clean but is unsafe:
The code may pass unit tests and still fail under production load because concurrency issues often need sustained traffic before they show up.
Safe Option 1: Create a Formatter Per Call
The most straightforward fix is to create a new formatter each time you need one. That gives every call its own state and avoids cross-thread interference completely.
For many services, this is the right answer. The small allocation cost is usually less important than correctness.
Safe Option 2: Use ThreadLocal
If number formatting sits in a very hot path and you want to avoid creating a new object on every call, ThreadLocal provides one formatter per thread.
This can be a good compromise, but only if you understand the lifetime of the threads involved. Long-lived thread pools keep their thread-local state around.
Locale-Aware Formatting
Sometimes the real requirement is locale-aware output rather than one fixed numeric pattern. In that case, use a locale-specific formatter and keep locale selection explicit.
Even then, avoid sharing one mutable formatter globally across all threads and users.
A Small Stress Test
Concurrency problems often hide until many calls hit the formatter at once. A simple stress test helps surface unsafe sharing:
This may not fail every run, which is exactly why the bug is dangerous. Shared mutable formatters can be "fine" until timing changes.
Choosing Between the Safe Options
Use local construction unless profiling proves it is too expensive. Reach for ThreadLocal only after you have a real throughput reason and a clear thread lifecycle. Avoid synchronized as a reflex fix unless you are comfortable serializing every formatting call through one lock.
Also remember that DateTimeFormatter from java.time is immutable and thread-safe, but that guarantee does not extend to DecimalFormat. Developers often assume all formatter types in Java behave the same way, and they do not.
Common Pitfalls
The most common mistake is declaring a shared formatter as static final and assuming final makes it thread-safe. It only makes the reference immutable, not the formatter's internal state.
Another issue is relying on light local testing. Concurrency bugs may not appear until executor pools, request bursts, or production-like traffic levels are involved.
Developers also forget that locale requirements can make a single global formatter logically wrong even before thread safety becomes a problem.
Finally, ThreadLocal is not free. It solves one problem, but it also creates lifecycle and cleanup considerations in managed runtimes.
Summary
- '
DecimalFormatinstances are mutable and not thread-safe.' - Sharing one formatter across threads can produce incorrect or unstable output.
- The simplest safe fix is to create a new formatter per call.
- '
ThreadLocalcan reduce allocation in hot paths, but it adds lifecycle complexity.' - Keep locale handling explicit and avoid global mutable formatters by default.

