Singleton Pattern in Scala: Ensuring a Single Instance with Thread Safety

Explore Singleton in Scala, when a plain object is sufficient, and when a single shared instance turns into hidden global state.

Singleton in Scala: A single shared instance made available through the language’s object construct or an explicitly managed module-level instance.

Scala makes Singleton look trivial because an object already gives you one instance. That solves the mechanical side of the pattern, but not the architectural side. The real question is not “how do I ensure one instance exists?” It is “should this thing really be global and shared?”

Scala Objects Solve the Construction Problem

If all you want is one JVM-local instance, Scala already provides it:

1object BuildInfo {
2  val version: String = "1.0.0"
3}

This is thread-safe in the usual Scala/JVM sense and avoids the double-checked-locking and static-holder ceremony that older languages often needed.

The Hard Part Is Scope, Not Syntax

Singleton becomes dangerous when “single instance” silently becomes:

  • hidden dependency
  • mutable global state
  • implicit service locator
  • environment-sensitive runtime object that tests must fight

That is why many Scala codebases use object freely for constants, utility namespaces, and stable stateless helpers, but avoid it for stateful services and external integrations.

Stateless Singletons Are Usually Fine

Good uses often include:

  • constants
  • pure utility functions
  • stable registries of values
  • codecs or format definitions
  • small module entry points with no mutable lifecycle

These work because the shared instance is not smuggling runtime state or environmental policy.

Stateful Singletons Need More Scrutiny

Trouble starts when the singleton owns:

  • configuration
  • caches
  • connection pools
  • clock or randomness policy
  • request-scoped information

At that point the “easy” global instance often becomes the hardest thing to isolate in tests or evolve across environments.

Prefer Explicit Injection for Stateful Capabilities

A service that talks to a database or external API may still have one runtime instance, but that does not mean it should be accessed as a global singleton. Explicit construction is often better:

1final class ExchangeRatesClient(http: HttpClient, config: RatesConfig)

There may still be one instance in production, but callers should receive it through assembly code, not by reaching into global state.

Singleton Is Also a Deployment Assumption

An object is single within one process, not across a distributed system. That sounds obvious, but it matters. Teams sometimes use singleton vocabulary where the real need is:

  • one leader in a cluster
  • one logical coordinator
  • one shared cache entry

Those are distributed coordination problems, not language-level singleton problems.

Common Failure Modes

Using object as a Hidden Service Locator

Dependencies become globally reachable and hard to reason about.

Putting Mutable Runtime State in a Global Singleton

Tests interfere with each other and environment-specific behavior becomes harder to isolate.

Confusing JVM-Local Single Instance With System-Wide Uniqueness

The language construct solves only the local instantiation problem.

Practical Heuristics

Use Scala object freely for stateless, stable, process-local concepts. Be much more careful with mutable or environment-specific services. If callers need to reason about configuration, lifecycle, or effects, inject a capability explicitly even if production uses only one instance.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026