NodeJSUncaught Exceptions

Uncaught Exceptions

Every error-handling strategy has a last line of defence. Even with careful try/catch and centralised middleware, a synchronous throw that escapes all handlers — a bug in a third-party library, an error in startup code, a deep callback you forgot about — would normally crash the process with an ugly stack trace and no cleanup. process.on('uncaughtException') lets you intercept that final throw, log it, and shut down gracefully rather than abruptly. But it is not a recovery mechanism — it's a controlled-crash mechanism.

What triggers it

JS
// A throw that escapes ALL try/catch in synchronous code:
setTimeout(() => {
  throw new Error('Something exploded')   // no wrapping try/catch
}, 1000)

// A missing property deep in a callback:
EventEmitter.on('data', (chunk) => {
  process(chunk.nested.value)             // TypeError if nested is undefined
})
The handler

JS
process.on('uncaughtException', (err, origin) => {
  // 'origin' is 'uncaughtException' or 'unhandledRejection' (Node 12+)
  logger.fatal({ err, origin }, 'Uncaught exception — process will exit')

  // Flush any buffered logs, notify alerting:
  // e.g. Sentry.captureException(err)

  // IMPORTANT: exit after logging — do NOT try to continue:
  process.exit(1)
})
Log then EXIT — never try to resume from an uncaught exception
The Node documentation is explicit: after an uncaught exception the application is in an **undefined state**. Database connections may be open mid-transaction, in-flight requests have no response, internal data structures may be corrupted. Attempting to continue serving requests from this state is dangerous — you risk silently serving wrong data or exposing partial writes. Log everything you can, flush buffers, then call `process.exit(1)`. A process supervisor (PM2, Kubernetes, systemd) will restart the process in a clean state.
What you can do in the handler
  • Log the full error — message, stack, and any request context you can access.

  • Notify alerting — Sentry, PagerDuty, Datadog, etc.

  • Flush log buffers — some loggers are asynchronous; give them a moment.

  • Call process.exit(1) — required; do not omit or defer it.

  • Do NOT — serve more requests, run async code that might throw again, or try to "recover."

The wrong way — catching to continue

JS
// ❌ DANGEROUS — the process is in an unknown state after this:
process.on('uncaughtException', (err) => {
  console.error('Caught:', err.message)
  // no process.exit() — the server keeps running in a corrupt state
})
Not calling `process.exit()` in the handler is more dangerous than crashing
It's tempting to catch the exception and let the process limp on — it feels like resilience. But a process that swallowed an uncaught exception has no guarantees about its memory, open connections, or in-progress requests. It may silently corrupt data, leak file descriptors, or serve stale state. Fast fail + supervisor restart is **more reliable** than continuing in an undefined state. The listener is for logging and clean shutdown, not for preventing the exit.
Don't use it as a substitute for proper error handling
`uncaughtException` is a last resort, not a general catch-all
Using `uncaughtException` to silently swallow errors that should have been caught at the source is an anti-pattern — it masks bugs and makes debugging nearly impossible. The right tool for async errors in Express handlers is [`try/catch + next(err)`](/nodejs/try-catch-async); for promise rejections it's [`unhandledRejection`](/nodejs/unhandled-rejections). The `uncaughtException` handler should rarely fire in a well-structured app. When it does fire, it means something slipped through — log it, fix the underlying bug, don't expand the listener's scope.
In production: pair with a supervisor

Bash
# PM2 restarts the process automatically on crash:
pm2 start app.js --name api

# Kubernetes: restartPolicy: Always (the default) does the same.
# systemd: Restart=always

# The pattern: process crashes cleanly (exit 1) → supervisor restarts it
# → new process starts in a known-good state.
Fast-fail + auto-restart is more reliable than trying to stay up
The combination of `process.exit(1)` on unrecoverable errors and a supervisor that auto-restarts is the production standard. It gives you: a clean process on each restart (no accumulated corrupt state), a clear crash log to investigate, and minimal downtime (restarts in milliseconds with PM2 or Kubernetes). Compare that to a process that keeps running in an unknown state — silent data corruption, hanging requests, and no indication anything went wrong.
Next
The async counterpart: [Unhandled Promise Rejections](/nodejs/unhandled-rejections).