Exception Handling in Ruby
begin/rescue/ensure, raising your own errors, and why rescuing StandardError beats rescuing everything.
2 min read
Things go wrong at runtime — bad input, a missing file, a failed network call. Ruby's exception handling lets you catch those failures and decide what happens next, instead of letting the program crash.
begin/rescue/ensure
begin
result = 10 / 0
rescue ZeroDivisionError => e
puts "Error: #{e.message}"
ensure
puts "This always runs, error or not"
endrescue catches a specific exception class (and its subclasses); ensure runs regardless of whether an error occurred — useful for cleanup like closing a file or a connection.
Rescuing specific classes, not everything
begin
File.read("missing.txt")
rescue Errno::ENOENT => e
puts "File not found: #{e.message}"
rescue StandardError => e
puts "Something else went wrong: #{e.message}"
endListing more specific rescue clauses first, more general ones after, mirrors how case/when matches top to bottom — the first matching rescue wins. Rescuing StandardError (or its subclasses) is normal; rescuing bare Exception is almost always wrong, because Exception also covers things like NoMemoryError and SystemExit — errors severe enough, or intentional enough, that swallowing them silently can hide real problems or prevent your program from exiting when it's supposed to.
# Avoid: catches far more than intended, including SystemExit
begin
risky_operation
rescue Exception => e
puts "Error: #{e.message}"
end
# Prefer: StandardError and its subclasses cover ordinary application errors
begin
risky_operation
rescue StandardError => e
puts "Error: #{e.message}"
endMethods can rescue without begin/end
Inside a method definition, rescue can attach directly, skipping the explicit begin:
def parse_number(input)
Integer(input)
rescue ArgumentError
nil
end
parse_number("42") # 42
parse_number("abc") # nilRaising your own errors
def withdraw(balance, amount)
raise ArgumentError, "amount must be positive" if amount <= 0
raise "insufficient funds" if amount > balance
balance - amount
endraise "message" raises a plain RuntimeError; raise SomeErrorClass, "message" raises a specific class. For anything beyond a quick script, prefer specific error classes over bare raise "..." — it lets calling code rescue precisely the failure it knows how to handle, rather than rescuing every possible RuntimeError and hoping the message matches.
Custom exception classes
class InsufficientFundsError < StandardError
def initialize(msg = "not enough funds for this withdrawal")
super
end
end
def withdraw(balance, amount)
raise InsufficientFundsError if amount > balance
balance - amount
end
begin
withdraw(100, 500)
rescue InsufficientFundsError => e
puts e.message
endCustom exceptions should subclass StandardError (never Exception directly), and are worth creating once your application has distinct failure modes calling code needs to tell apart — a PaymentDeclinedError and a InvalidCardError deserve different handling, and separate classes make that possible instead of parsing an error message string to figure out what went wrong.
retry
attempts = 0
begin
attempts += 1
risky_network_call
rescue Timeout::Error
retry if attempts < 3
raise
endretry jumps back to the begin, re-running the block — useful for transient failures like a flaky network call, guarded by an attempt counter so it doesn't retry forever.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.