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
objectconstruct 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?”
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.
Singleton becomes dangerous when “single instance” silently becomes:
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.
Good uses often include:
These work because the shared instance is not smuggling runtime state or environmental policy.
Trouble starts when the singleton owns:
At that point the “easy” global instance often becomes the hardest thing to isolate in tests or evolve across environments.
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.
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:
Those are distributed coordination problems, not language-level singleton problems.
object as a Hidden Service LocatorDependencies become globally reachable and hard to reason about.
Tests interfere with each other and environment-specific behavior becomes harder to isolate.
The language construct solves only the local instantiation problem.
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.