Question

Why `rescue Exception` Is Bad Style in Ruby

rubyexception

Question

In Ruby, why is it considered bad style to write code like this?

begin
  do_something
rescue Exception => e
  puts e.message
end

A Ruby quick reference warns: “Don’t rescue Exception. EVER.” Why is rescuing Exception a problem, and what should be used instead for proper error handling?

Short Answer

By the end of this page, you will understand why rescue Exception is usually too broad in Ruby, how Ruby’s exception hierarchy works, and what you should rescue instead in everyday programs. You will also learn safer patterns for error handling, logging, retries, and validation in real Ruby code.

Concept

Ruby exceptions are organized in a class hierarchy. At the top is Exception, and many different error types inherit from it.

When you write:

rescue Exception => e

you are catching almost everything, including exceptions that usually should not be intercepted by normal application code.

Why this is a problem

Exception includes not only typical program errors, but also system-level and control-flow-related exceptions such as:

  • SystemExit — raised when the program is trying to exit
  • Interrupt — raised when the user presses Ctrl+C
  • NoMemoryError — raised when memory runs out
  • SignalException — related to operating system signals

If you rescue Exception, your program may:

  • ignore shutdown requests
  • refuse to stop on Ctrl+C
  • hide serious system failures
  • continue running in a broken or unsafe state

That makes debugging harder and can create surprising behavior.

What Ruby rescues by default

A plain rescue without specifying a class rescues StandardError, not Exception:

begin
  do_something
rescue => e
  puts e.message
end

This is the normal behavior Ruby expects for application-level error handling.

Why StandardError is usually correct

Most everyday errors inherit from StandardError, including:

  • RuntimeError
  • NoMethodError
  • ArgumentError
  • NameError
  • IOError
  • many library-specific errors

These are the kinds of exceptions your app can often log, handle, retry, or turn into user-friendly messages.

General rule

  • Rescue the most specific exception class you expect.
  • If you need a broad catch-all for application errors, rescue StandardError.
  • Avoid rescuing Exception unless you have a very unusual reason and fully understand the consequences.

Mental Model

Think of exceptions like alarms in a building.

  • StandardError alarms are room-level problems: a broken light, a locked door, a small leak.
  • Exception includes those alarms plus the building fire alarm, evacuation signal, and power failure warnings.

If you silence all alarms, you might also ignore the ones telling everyone to leave the building.

That is what rescue Exception does: it catches not only normal program problems, but also signals that the process should stop or cannot safely continue.

Take Quiz

Syntax and Examples

Preferred syntax

In Ruby, these are the common forms:

begin
  risky_operation
rescue StandardError => e
  puts "Handled: #{e.message}"
end

Or simply:

begin
  risky_operation
rescue => e
  puts "Handled: #{e.message}"
end

A bare rescue is shorthand for rescuing StandardError.

Better: rescue specific exceptions

begin
  number = Integer(user_input)
rescue ArgumentError
  puts "Please enter a valid integer"
end

This is better because it only handles the error you actually expect.

Example: too broad

begin
  exit
rescue Exception => e
  puts "Caught: #{e.class}"
end

This can catch SystemExit, which prevents the program from exiting normally.

Example: safer version

begin
  value = Integer("abc")
rescue ArgumentError => e
  puts "Conversion failed: #{e.message}"
end

Why this is good

  • It documents what can go wrong.
  • It avoids swallowing unrelated exceptions.
  • It makes debugging easier.
  • It keeps system-level exceptions untouched.

Step by Step Execution

Consider this example:

begin
  puts "Start"
  number = Integer("abc")
  puts "Number: #{number}"
rescue ArgumentError => e
  puts "Handled error: #{e.message}"
end

puts "Program continues"

Step-by-step

  1. Ruby enters the begin block.
  2. puts "Start" runs.
  3. Ruby executes Integer("abc").
  4. Ruby cannot convert "abc" to an integer.
  5. It raises an ArgumentError.
  6. Ruby looks for a matching rescue clause.
  7. rescue ArgumentError => e matches the raised exception.
  8. The rescue block runs and prints the error message.
  9. Execution continues after the end.
  10. puts "Program continues" runs.

Output

Start
Handled error: invalid value for Integer(): "abc"
Program continues

What if we had used rescue Exception?

It would also catch this error, but it would catch far more than needed. That makes the code less precise and more dangerous.

Real World Use Cases

1. Validating user input

begin
  age = Integer(params[:age])
rescue ArgumentError, TypeError
  age = nil
end

Used when form input may be invalid.

2. Reading files safely

begin
  content = File.read("config.txt")
rescue Errno::ENOENT
  content = "default config"
end

Used when a file might not exist.

3. Network and API calls

begin
  response = api_client.fetch_user(id)
rescue Timeout::Error
  puts "Request timed out"
end

Used for temporary failures that can be retried or reported.

4. Background jobs

begin
  process_job(job)
rescue StandardError => e
  logger.error(e.message)
  logger.error(e.backtrace.join("\n"))
end

A job runner may rescue StandardError to log failures and mark a job as failed, but should still avoid rescuing Exception.

5. Command-line tools

CLI tools often allow Ctrl+C to stop execution. Rescuing Exception may block that behavior by catching Interrupt.

Take Quiz

Real Codebase Usage

In real projects, developers rarely want a giant catch-all. They usually use one of these patterns.

Guard clauses before exceptions happen

Instead of rescuing a broad error, check inputs early:

def divide(a, b)
  return nil if b == 0
  a / b
end

Rescue only expected failures

def parse_port(value)
  Integer(value)
rescue ArgumentError, TypeError
  3000
end

Log and re-raise when needed

def save_record(record)
  record.save!
rescue ActiveRecord::RecordInvalid => e
  logger.warn("Validation failed: #{e.message}")
  raise
end

This pattern keeps visibility while preserving the original failure.

Wrap low-level errors in domain-specific errors

class PaymentError < StandardError; end

def charge_customer(client, amount)
  client.charge(amount)
rescue Timeout::Error, SocketError => e
  raise PaymentError, "Payment service unavailable: #{e.message}"
end

This gives higher-level code a cleaner error to handle.

Top-level app boundaries

Sometimes an application entry point rescues StandardError to log unhandled errors:

begin
  run_app
rescue StandardError => e
  logger.fatal(e.message)
  logger.fatal(e.backtrace.join("\n"))
  exit(1)
end

Even here, Exception is usually still too broad.

Common Mistakes

Mistake 1: Rescuing everything

begin
  do_work
rescue Exception => e
  puts "Something went wrong"
end

Why it is bad

  • hides important failures
  • catches Ctrl+C interrupts
  • may prevent clean shutdown

Better

begin
  do_work
rescue StandardError => e
  puts "Something went wrong: #{e.message}"
end

Or rescue a specific error class.

Mistake 2: Swallowing errors silently

begin
  do_work
rescue => e
end

Why it is bad

The program fails, but you get no clue why.

Better

begin
  do_work
rescue => e
  warn e.message
  raise
end

If you handle an error, do it intentionally.

Mistake 3: Using exceptions for normal control flow

def admin_name(user)
  begin
    user.admin.name
  rescue
    "Unknown"
  end
end

Why it is bad

This can hide unrelated bugs like misspelled method names.

Better

def admin_name(user)
  return "Unknown" unless user&.admin
  user.admin.name
end

Mistake 4: Rescuing the wrong class

begin
  File.read("missing.txt")
rescue StandardError
  puts "Bad input"
end

Problem

This works, but it is too general.

Better

begin
  File.read("missing.txt")
rescue Errno::ENOENT
  puts "File not found"
end

Comparisons

ApproachWhat it catchesTypical useRisk level
rescue ExceptionAlmost all exceptions, including system-level onesVery rare, special infrastructure codeHigh
rescue StandardErrorMost normal application errorsGeneral app-level error handlingMedium
rescue SomeSpecificErrorOnly the named error typeBest for expected failuresLow
bare rescueSame as rescue StandardErrorShort form in simple casesMedium

Exception vs StandardError

  • Exception is the root of Ruby’s exception hierarchy.
  • StandardError is the normal parent class for application errors.
  • Most code should rescue StandardError or a subclass.

Specific rescue vs broad rescue

StyleExampleBenefitDrawback
Specificrescue ArgumentErrorPrecise and clearMay need multiple rescue clauses
Broad app-levelrescue StandardErrorConvenient for logging or fallbackCan still hide unrelated app bugs
Too broadrescue ExceptionCatches nearly everythingDangerous and misleading
Take Quiz

Cheat Sheet

Quick rules

  • Do not rescue Exception in normal Ruby code.
  • Use rescue => e or rescue StandardError => e for general application errors.
  • Prefer rescuing the specific exception class you expect.
  • Log errors when helpful.
  • Re-raise errors if you cannot truly handle them.

Common syntax

begin
  risky_code
rescue => e
  puts e.message
end
begin
  risky_code
rescue ArgumentError => e
  puts e.message
end
begin
  risky_code
rescue ArgumentError, TypeError => e
  puts e.message
end

Good defaults

  • Expected parse failure: ArgumentError
  • Missing file: Errno::ENOENT
  • Validation failure: library-specific error
  • Generic app boundary: StandardError

Avoid

rescue Exception => e

unless you are writing very low-level infrastructure and intentionally handling system-level exceptions.

Important idea

A bare rescue in Ruby means:

rescue StandardError

FAQ

Why is rescue Exception bad in Ruby?

Because it catches far more than normal application errors, including SystemExit and Interrupt, which usually should not be blocked.

What should I rescue instead in Ruby?

Usually rescue a specific exception class. If you need a broad catch for app errors, rescue StandardError.

Does bare rescue catch Exception?

No. A bare rescue catches StandardError by default.

Can rescue Exception stop Ctrl+C from working?

Yes. Ctrl+C raises Interrupt, which is under Exception. Rescuing Exception can prevent the program from stopping normally.

Is it ever okay to rescue Exception?

Rarely. It may be used in very low-level framework or process-management code, but it is not appropriate for ordinary application logic.

What is the difference between Exception and StandardError?

Exception is the top-level parent of Ruby exceptions. StandardError is the common parent for normal application errors.

Should I always rescue a specific error type?

Yes, when possible. It makes the code clearer and avoids accidentally hiding unrelated problems.

Take Quiz

Mini Project

Description

Build a small Ruby script that reads a filename from the user, tries to open the file, and converts each line to an integer. This demonstrates how to rescue specific exceptions instead of using Exception, and how to keep different error cases separate and understandable.

Goal

Create a script that safely handles missing files and invalid number input using specific Ruby exception classes.

Requirements

  1. Ask the user for a filename.
  2. Read all lines from the file.
  3. Convert each line to an integer.
  4. If the file does not exist, print a helpful message.
  5. If a line is not a valid integer, print which line failed.
  6. Do not use rescue Exception.
Take Quiz

Keep learning

Related questions

View more