Core Java

Let It Crash vs. Try-Catch: Erlang/BEAM Supervision Trees Against Java’s Exception Model

There’s a moment every Java developer has lived through: staring at a stack trace at 2 a.m., trying to figure out whether to catch this exception, wrap it, rethrow it, or just let the whole request die. Somewhere in an Erlang shop, a similar failure just happened, and nobody’s awake to see it. The process crashed, its supervisor noticed, restarted it in a clean state, and the system kept serving traffic. Same category of problem, two completely different philosophies about what to do when code breaks.

This isn’t a minor stylistic difference. It’s a genuine fork in how an entire ecosystem thinks about reliability, and the data from real telecom deployments backs up just how far that fork actually goes.

What is the “let it crash” philosophy in Erlang/BEAM?

“Let it crash” is a fault-tolerance philosophy where individual processes are allowed to fail immediately on encountering an error, rather than attempting defensive recovery inline. A supervising process detects the crash and restarts the failed component in a known-good state. Instead of anticipating every failure mode, developers isolate failures and let a separate structure handle recovery automatically.

Java’s model asks a different question entirely. It asks: what should this specific piece of code do when this specific thing goes wrong? Erlang asks: how do we build a system where it doesn’t matter which piece of code broke, because recovery is architectural, not local.

Why Java’s exception model puts recovery on the code that failed

Java’s try/catch/finally structure has been the backbone of error handling since the language shipped, and it’s a genuinely reasonable model for a lot of problems. Checked exceptions force you to acknowledge failure modes at compile time. Custom exception hierarchies let you express domain-specific failure semantics. Resource management via try-with-resources closes the loop on cleanup.

The friction shows up at scale. Every method that can fail has to decide, locally, what failing actually means for its caller. Catch too broadly and you swallow bugs you didn’t know existed. Catch too narrowly and unexpected exceptions propagate up through layers that were never designed to handle them, often crashing an entire thread or, in older Servlet-based architectures, taking down request-handling capacity with it. Anders Hejlsberg’s well-known critique of checked exceptions, that they push developers toward blanket catch (Exception e) blocks just to satisfy the compiler, describes exactly this failure pattern: defensive code that technically compiles but doesn’t actually handle anything meaningfully.

Java has gotten better tools for isolating failure over the years, virtual threads and structured concurrency chief among them, and a crashed thread today doesn’t take down a whole JVM the way an unhandled error in a shared-memory C program might. But the fundamental unit of recovery in Java is still, overwhelmingly, “the code that noticed the problem decides what to do about it.” There’s no equivalent of a supervisor sitting above your object graph, watching for failures it didn’t have to predict in advance.

How Erlang’s supervision trees actually work

Erlang, and the BEAM virtual machine it runs on, were built from 1986 onward at Ericsson’s Computer Science Laboratory specifically to solve a problem Java’s designers weren’t trying to solve: telephone exchanges that genuinely could not be allowed to go down. Joe Armstrong’s 2003 PhD thesis, titled “Making reliable distributed systems in the presence of software errors,” formalized the reasoning behind this: telecom applications are large enough that, despite careful testing, they will contain errors once they’re in service, so the right engineering response is building systems that tolerate errors rather than systems that pretend errors won’t happen.

The mechanism is a supervision tree. A supervisor process starts, monitors, and restarts a set of child processes according to a declared restart strategy, one_for_one restarts just the failed child, one_for_all restarts every sibling alongside it, rest_for_one restarts the failed child and everything started after it. Because Erlang processes share no memory and communicate only by message passing, a crash in one process genuinely cannot corrupt the internal state of another. That isolation guarantee is what makes “just let it crash and restart” a sound engineering strategy rather than a reckless one. The diagram above shows the shape of this directly, a supervisor managing three workers, one crashing, and its siblings continuing to run completely untouched by the failure.

This is a structural difference from Java’s model, not just a syntactic one. In Java, the boundary of failure containment is usually whatever try block or thread boundary a developer chose to draw. In Erlang, it’s an explicit, declared part of the application’s architecture, visible in the supervision tree itself, and it exists whether or not any individual developer thought carefully about this particular failure mode in advance.

What the telecom uptime numbers actually show

Here’s where this stops being philosophical and starts being measurable. Ericsson’s AXD301 ATM switch, an early flagship deployment for Erlang, ran on roughly two million lines of Erlang code handling high-throughput data routing for a telecom customer. The figure most often repeated about it is “nine nines” of reliability, an uptime of 99.9999999%, a number that would allow only about 31 milliseconds of downtime per year.

That number deserves real scrutiny, not just repetition. Armstrong himself treated the nine-nines figure as uncertain in his own thesis, noting that evidence for the system’s long-term operational stability hadn’t been collected systematically, and independent commentators have pointed out that a lot of the AXD301’s reliability likely came from careful engineering in surrounding C code and hardware redundancy, not from Erlang alone. Even skeptics of the exact figure generally concede the broader point: this was a British Telecom-deployed, carrier-class switch that ran for years with vanishingly small measured downtime, and that’s a genuinely different reliability tier than most enterprise Java deployments target or achieve.

The chart above puts that claim in context against the standard availability tiers used across the industry. Five nines allows about 5.3 minutes of downtime per year, six nines about 32 seconds, seven nines about 3.2 seconds, and the widely accepted view is that anything beyond seven or eight nines is at the edge of what’s even meaningfully measurable over a normal observation period. The AXD301’s claimed nine-nines figure sits so far below that line that it’s genuinely hard to verify empirically, which is exactly why Armstrong flagged it as uncertain rather than proven. Treat the specific number with appropriate skepticism, but treat the underlying architectural pattern, isolated processes plus automatic supervised restart, as the real, reproducible source of Erlang’s reliability reputation.

WhatsApp’s later use of Erlang tells a less disputed, more operationally verifiable story about the same underlying properties. The company famously scaled to handling roughly a million concurrent connections on a single machine, a scale that owes as much to lightweight process isolation as it does to supervision, but the two properties come from the same design decisions and reinforce each other in production.

What Java teams can actually borrow from this

None of this means Java teams need to abandon exceptions or rewrite their stack in Elixir. It means recognizing that some of Erlang’s most valuable ideas are architectural patterns, not language features, and several of them translate reasonably well.

  • Treat failure isolation as a structural decision, not just a coding discipline. Circuit breakers, bulkheads, and supervisor-style process managers (a pattern increasingly common in microservice orchestration) do a version of what OTP supervisors do natively.
  • Favor fast, visible failure over silent degradation. A service that crashes loudly and restarts clean is often more reliable in practice than one that limps along in a half-broken state nobody notices.
  • Push recovery logic up and out of business logic wherever possible, rather than scattering defensive catch blocks through every layer that might encounter a downstream failure.

The gap that’s genuinely hard to close is process-level memory isolation combined with sub-millisecond restart, which is baked into the BEAM VM at a level the JVM’s shared-memory threading model doesn’t offer natively. That’s a real, structural advantage Erlang keeps, not just a mindset difference.

Frequently Asked Questions (FAQ)

Is “let it crash” the same thing as ignoring errors?
No, and this is probably the most common misunderstanding of the philosophy. Every crash is caught and handled, just at a different layer than the code that failed. The supervisor is actively watching and responding; the difference is that recovery logic lives in dedicated supervision structure rather than scattered try/catch blocks throughout business logic.

Can Java achieve similar reliability without switching languages?
Java can get meaningfully closer using patterns like circuit breakers, bulkhead isolation, and dedicated process supervisors, especially in containerized environments where an orchestrator like Kubernetes effectively plays a supervisor-like restart role at the pod level. It won’t match BEAM’s per-process memory isolation and lightweight restart speed, but the architectural gap is narrower than the raw uptime numbers might suggest.

Was the AXD301’s “nine nines” claim ever independently verified?
Not with the kind of systematic measurement that would satisfy a strict reliability audit, and Armstrong acknowledged that gap in his own thesis. The figure is best treated as a widely repeated, plausible-but-unconfirmed claim rather than a peer-reviewed benchmark, even though the underlying architecture’s reliability properties are well documented and widely reproduced elsewhere.

Does the JVM have anything equivalent to an Erlang supervisor?
Not natively at the language level, though Akka (built on the JVM) explicitly ported the actor and supervision model from Erlang for use in Java and Scala systems. Outside of that, most JVM teams approximate supervision through external orchestration layers rather than in-process constructs.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button