Exception Handling in Ruby
Things go wrong: files are missing, network calls time out, user input is malformed, division happens by zero. Ruby handles these situations through exceptions — objects that represent an error condition and can be raised, caught, and inspected. Ruby's exception-handling vocabulary (begin/rescue/else/ensure/end, along with the raise keyword) is deliberately similar to try/catch/finally in other languages, but with Ruby's own conventions: exceptions are ordinary objects organized in a class hierarchy rooted at Exception, and rescuing the wrong class — or rescuing too broadly — is one of the most common sources of subtle bugs in production Ruby code.
Cricket analogy: Rain stopping play, a run-out review going wrong, or a bat breaking mid-shot are all things that go wrong on the field; Ruby's begin/rescue/ensure structure is the umpire's protocol for handling disruptions without abandoning the match, but rescuing the wrong class is like stopping play for a non-issue.
begin/rescue/else/ensure
A begin block wraps code that might fail. One or more rescue clauses catch specific exception classes and handle them. An optional else clause runs only if no exception was raised, and ensure always runs, whether or not an exception occurred — making it the natural place for cleanup like closing files or releasing locks.
Cricket analogy: A begin block is like attempting a risky run between wickets; rescue clauses are the specific recovery plans for a run-out versus a dropped bat, else runs only if the run was completed safely, and ensure always happens — like the physio checking on the batsman regardless.
def read_config(path)
file = File.open(path)
contents = file.read
JSON.parse(contents)
rescue Errno::ENOENT => e
warn "Config file missing: #{e.message}"
{}
rescue JSON::ParserError => e
warn "Config file is not valid JSON: #{e.message}"
{}
else
puts "Config loaded successfully"
ensure
file&.close
endMethods (and blocks in modern Ruby) can use rescue/ensure directly without an explicit begin/end wrapper, as shown in the example above — begin is only required when you want to rescue within an arbitrary code block rather than a whole method body.
The exception hierarchy and raise
Ruby's built-in exceptions descend from Exception, with StandardError as the ancestor of most everyday errors (ArgumentError, TypeError, ZeroDivisionError, RuntimeError, IOError, and so on). A bare rescue with no class specified only catches StandardError and its subclasses — it deliberately does not catch things like SystemExit, NoMemoryError, or Interrupt (Ctrl-C), which descend directly from Exception. This is why rescue Exception is almost always wrong: it swallows signals and system-level conditions the program should not silently absorb.
Cricket analogy: StandardError is like the everyday match incidents (no-balls, wides, dropped catches) a rescue clause is meant to handle, while things like the stadium losing power entirely descend from a more catastrophic category that no ordinary rescue Exception should silently absorb.
def divide(a, b)
raise ArgumentError, "divisor cannot be zero" if b.zero?
a / b
end
begin
divide(10, 0)
rescue ArgumentError => e
puts "Bad input: #{e.message}"
end
# raise with no arguments re-raises the current exception inside a rescue block
begin
begin
raise "inner failure"
rescue => e
puts "logging and re-raising"
raise
end
rescue => e
puts "outer caught: #{e.message}" #=> outer caught: inner failure
endrescue => e (no explicit class) is shorthand for rescue StandardError => e, not for rescuing everything. Beginners often assume a bare rescue catches all errors — it does not catch Exception subclasses outside StandardError, which is almost always the behavior you want.
retry and controlling flow
The retry keyword, used inside a rescue clause, jumps back to the beginning of the begin block and runs it again — useful for transient failures like a flaky network call, provided you guard against infinite retry loops with a counter or a maximum attempt limit.
Cricket analogy: retry is like a bowler who overstepped and bowled a no-ball getting to bowl that delivery again immediately, but a captain still caps how many times a bowler can be recalled for the same over to avoid an endless loop of no-balls.
attempts = 0
begin
attempts += 1
response = fetch_from_flaky_api
rescue Net::OpenTimeout => e
if attempts < 3
sleep(attempts) # simple backoff
retry
else
raise "Giving up after 3 attempts: #{e.message}"
end
end- begin/rescue/else/ensure/end handles errors; ensure always runs, else only runs when no exception occurred.
- Methods can rescue directly without a begin wrapper, but blocks need an explicit begin/end.
- A bare rescue catches StandardError and subclasses only — never rescue Exception directly.
- raise with a class and message creates a new exception; bare raise inside a rescue re-raises the current one.
- retry restarts the begin block, so always pair it with an attempt counter to avoid infinite loops.
- Custom, specific exception classes should be preferred over generic RuntimeError for anything an API caller needs to distinguish.
Practice what you learned
1. What does a bare `rescue` (with no exception class specified) actually catch?
2. Which clause in a begin/rescue block runs only if no exception was raised?
3. What does calling `raise` with no arguments do when used inside a rescue block?
4. Why is `rescue Exception => e` generally considered bad practice?
5. What must you guard against when using `retry` inside a rescue clause?
Was this page helpful?
You May Also Like
Custom Exceptions in Ruby
Learn to design your own exception classes by subclassing StandardError, giving callers precise, catchable error types instead of generic RuntimeErrors or string messages.
Common Ruby Pitfalls
A tour of frequent Ruby mistakes — mutable defaults, frozen string surprises, nil handling, and block/proc return semantics — with fixes for each.
method_missing and respond_to?
Explore Ruby's dynamic dispatch fallback: how method_missing intercepts unknown method calls, and why it must be paired with respond_to_missing? for correct, well-behaved objects.
Ruby Interview Questions
A curated set of common Ruby interview questions and concise, accurate answers spanning language fundamentals, object orientation, and idiomatic style.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics