When a Go program misbehaves at runtime, the stack stops with a panic that can crash your service. Understanding how to catch and handle panic in idiomatic Go helps you build resilient systems that fail safely and provide clear diagnostics.
This guide shows practical patterns for recovering from unexpected panics, comparing deliberate recover usage with proactive error handling, and choosing the right strategy for each service boundary.
| Aspect | When to Panic | When to Return an Error | Recovery Strategy |
|---|---|---|---|
| Startup initialization | Invalid configuration that makes the program unusable | Configuration is wrong but defaults allow fallback | Log and exit if unrecoverable at startup |
| Request handling | Programming bugs discovered during development | Bad input, downstream failures, or transient issues | Use recover in middleware to log and return 5xx safely |
| Worker loops | Corrupt data that indicates memory or logic corruption | Expected business rule violations or validation failures | Recover to keep loop alive and report health degradation |
| Public API boundaries | Never acceptable in production code paths | Always invalid input, policy violations, or timeouts | Recover at edge, log stack, and map to structured error response |
Recovering from Panic in Handler Code
In HTTP services, an unhandled panic bubbles up to the runtime and shuts down the connection. The standard library provides the built-in recover function to intercept that panic inside a deferred function.
By placing a recover call at the edge of request handling, you can convert a crashing path into a controlled response, log the stack, and keep the goroutine alive for future work. This pattern is common in frameworks and idiomatic middleware to protect availability.
A careful approach logs the stack, sets a clear status code, and avoids swallowing errors that should surface as regular error handling rather than exceptional control flow.
Best Practices for Controlled Recovery
Use recover only near well-defined boundaries such as HTTP handlers, gRPC interceptors, or message processing loops. Inside those scopes, a named return value can capture a cleaned error result while still catching unexpected panics.
Always include the stack trace when you recover, because the value returned by recover alone is often just a string without context. Combining runtime stack with structured logging gives operators the information needed to debug production incidents.
Keep recovery logic small and focused; do not use panic as a substitute for normal error handling in business logic, and prefer returning explicit errors for expected failure cases.
Designing Services Around Panic Safety
Service boundaries should be panic-aware, with middleware that recovers, logs, and optionally applies backpressure or circuit breakers. This keeps individual faults from cascading across the system.
For long-running workers, a top-level loop recover prevents a single corrupt message from taking down the entire goroutine, while still surfacing the issue through metrics and alerts. You can isolate critical sections and protect shared state without overusing panic in everyday code.
Combine recover patterns with health checks and graceful shutdown so that repeated panics trigger restarts rather than silent degradation of service quality. Instrumentation and observability turn raw panic data into actionable reliability insights.
Testing and Observability of Panic Recovery
To validate your recovery code, write tests that induce a panic in a controlled handler and assert that the response is safe, logs are produced, and metrics are incremented. Table-driven tests make it easy to cover scenarios such as recoverable versus unrecoverable situations.
Observability pipelines should correlate recovered panics with request IDs, trace spans, and deployment versions to accelerate root cause analysis. Alerting on spike patterns helps you catch systemic issues before they trigger full outages.
Key Takeaways for Robust Go Services
- Use recover at service edges such as HTTP and messaging boundaries to protect against unexpected crashes.
- Always capture and log the stack when you recover; raw panic values are rarely enough for debugging.
- Prefer explicit error returns for expected failures and reserve panic for exceptional, unrecoverable conditions.
- Isolate recover to small, well-scoped functions so that recovery behavior is predictable and testable.
- Combine recovery patterns with health checks, circuit breakers, and observability to build resilient, production-ready systems.
FAQ
Reader questions
Should I use recover to handle invalid user input in my HTTP handlers?
No, use regular error handling for invalid input; reserve recover for truly exceptional situations such as programming bugs or unexpected runtime states so that your error semantics stay consistent and observable.
Can recover in one goroutine catch a panic from another goroutine?
No, recover only observes panics in the same goroutine where it is called; cross-goroutine panics must be handled by deferring recover inside the goroutine that might panic or by explicit supervision patterns.
Is it safe to log the stack inside recover and continue processing?
Yes, for many request-boundary scenarios it is safe to log the stack, return a controlled response, and continue, but first ensure that the state remains consistent and no invariants are violated by the panic.
How can I test that my recover logic works correctly in production-like conditions?
Inject controlled panics in integration tests, verify that metrics and logs are emitted, and confirm that downstream clients receive the expected safe status codes without crashing the service.