Error Handling with begin/rescue

· 4 min read · Updated March 7, 2026 · beginner
error-handling exceptions begin-rescue beginner

Errors happen. Your code might try to divide by zero, open a file that doesn’t exist, or receive unexpected data from a user. Without error handling, any exception crashes your program. With error handling, you can recover gracefully and provide useful feedback.

In this tutorial, you’ll learn how Ruby’s begin, rescue, ensure, and raise keywords work together to handle errors elegantly.

The Basic Pattern: begin and rescue

Ruby provides a way to catch and handle exceptions using begin and rescue:

begin
  # Code that might raise an error
  result = 10 / 0
rescue
  # Code that runs when an error occurs
  puts "Something went wrong!"
end

When Ruby encounters an error inside the begin block, it immediately jumps to the rescue block. The 10 / 0 raises a ZeroDivisionError, which triggers the rescue block.

Why This Matters

Without rescue, your program crashes:

# Without begin/rescue - program crashes
result = 10 / 0
# => ZeroDivisionError: divided by 0

With rescue, you control what happens:

# With begin/rescue - graceful handling
begin
  result = 10 / 0
rescue ZeroDivisionError
  puts "Cannot divide by zero!"
  result = 0  # Provide a safe default
end

puts result  # => 0

Handling Specific Exception Types

Ruby has many exception types. Catching all exceptions with a bare rescue works but isn’t specific. It’s better to handle specific exception types:

begin
  file = File.open("nonexistent.txt")
  content = file.read
rescue Errno::ENOENT
  puts "File not found!"
rescue Errno::EACCES
  puts "Permission denied!"
end

This pattern handles different errors differently. Errno::ENOENT means “error, no entity” — the file doesn’t exist. Errno::EACCES means access denied.

Common Exception Types

ExceptionWhen It Occurs
StandardErrorMost user-code errors inherit from this
ArgumentErrorWrong number of arguments
NameErrorUndefined variable or method
TypeErrorWrong type used in operation
ZeroDivisionErrorDividing by zero
NoMethodErrorCalling undefined method

Catch StandardError for most user-written code errors. Avoid catching all exceptions (rescue Exception or rescue StandardError) unless you truly need to.

Storing the Exception

Ruby stores the exception object in a variable you can access:

begin
  File.open("missing.txt")
rescue Errno::ENOENT => e
  puts "Error: #{e.message}"
  puts "Class: #{e.class}"
end

The => e syntax captures the exception object. You can then access:

  • e.message — the error message
  • e.class — the exception type
  • e.backtrace — where the error occurred

Using ensure: Code That Always Runs

Sometimes you need code that runs whether or not an error occurs — like closing a file or releasing a resource. That’s what ensure is for:

file = File.open("data.txt", "w")
begin
  file.write("Hello")
  raise "Something went wrong!"
rescue
  puts "Error occurred: #{$!}"
ensure
  file.close  # Always runs
end

The ensure block runs whether:

  • The code succeeds
  • An exception is raised and rescued
  • An exception is raised and not rescued

Real-World Example: Database Connection

def fetch_users
  db = Database.connect
  begin
    db.query("SELECT * FROM users")
  rescue DatabaseError => e
    puts "Database error: #{e.message}"
    []
  ensure
    db.close if db
  end
end

This pattern ensures the database connection always closes, even when errors occur.

Using retry to Try Again

Sometimes an error is temporary — like a network timeout. You can use retry to run the begin block again:

begin
  response = HTTP.get("https://api.example.com/data")
rescue Timeout::Error
  puts "Request timed out, retrying..."
  retry  # Runs the begin block again
end

Be careful with retry: always add a limit to prevent infinite loops:

attempt = 0
begin
  attempt += 1
  response = HTTP.get("https://api.example.com/data")
rescue Timeout::Error
  if attempt < 3
    puts "Retry ##{attempt}"
    retry
  else
    puts "Failed after 3 attempts"
  end
end

Raising Your Own Exceptions

You don’t have to wait for Ruby to raise exceptions. Use raise to trigger your own:

def withdraw(amount)
  raise ArgumentError, "Amount must be positive" if amount <= 0
  raise InsufficientFundsError, "Not enough money" if amount > balance
  # ... process withdrawal
end

You can raise any exception class. Ruby includes built-in ones, or create your own:

class MyCustomError < StandardError
end

raise MyCustomError, "Something went wrong!"

When to Use Error Handling

Use error handling when:

  • External resources might be unavailable (files, networks, databases)
  • User input might be invalid
  • Operations might fail for reasons outside your control

Avoid using error handling for:

  • Expected conditions that are normal (check for nil first)
  • Flow control (use if/else instead)
  • Every possible problem (keep it simple)

Summary

Ruby’s error handling uses begin to mark code that might fail, rescue to catch and handle specific errors, ensure for cleanup code that always runs, and raise to trigger your own exceptions.

Key points to remember:

  • Always rescue specific exception types, not all exceptions
  • Use ensure for cleanup code that must run
  • Add retry limits to prevent infinite loops
  • Handle errors at the appropriate level of your program

Next Steps

Now that you understand error handling, continue with the next tutorial in the ruby-fundamentals series: “File I/O in Ruby” where you’ll learn how to read and write files while handling errors gracefully.