Practical examples of Ruby exception handling examples for real apps

If you write Ruby code long enough, something will blow up at runtime—files go missing, APIs time out, user input gets weird. That’s where practical examples of Ruby exception handling examples start to matter. Not in theory, but in the real world, where a clean error message beats a stack trace dumped in production. This guide walks through modern, realistic examples of Ruby exception handling examples you can drop into Rails apps, background jobs, CLI tools, and API clients. Instead of abstract patterns, you’ll see how to rescue only what you mean to rescue, log what matters, and avoid silently hiding bugs. We’ll also touch on how exception handling has evolved in the Ruby ecosystem through 2024–2025, including better logging, observability, and integration with error tracking services. If you’ve ever wrapped everything in a giant `rescue StandardError` and hoped for the best, this is for you. Let’s look at better, concrete patterns that keep your Ruby code honest—and your users calm.
Written by
Jamie
Published

Simple examples of Ruby exception handling examples with begin / rescue

Before getting fancy, it helps to see a small, honest example of Ruby exception handling that you might actually type into irb.

begin
  puts "Enter a number:"
  input = gets.chomp
  number = Integer(input)   # may raise ArgumentError
  puts "You entered: #{number}"
rescue ArgumentError => e
  warn "Invalid number: #{e.message}"
end

This is one of the best examples for beginners because it shows three important habits:

  • Rescue a specific exception (ArgumentError), not everything.
  • Use the exception object (e) for a meaningful message.
  • Let other exceptions bubble up so you still see real bugs.

If you want a softer UX, you can loop until the user gives valid input, but the core pattern stays the same.


File I/O: example of handling missing or unreadable files

File handling is where you see some of the clearest examples of Ruby exception handling examples in everyday scripts. Files disappear, permissions change, encodings get messy.

file_path = "config/settings.yml"

begin
  content = File.read(file_path)
  puts "Loaded settings (#{content.bytesize} bytes)"
rescue Errno::ENOENT => e
  warn "Config file not found: #{file_path}"
rescue Errno::EACCES => e
  warn "Permission denied reading: #{file_path}"
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e
  warn "Bad encoding in file #{file_path}: #{e.class} - #{e.message}"
end

Real examples like this highlight why you rarely want to rescue StandardError in one big net. Different file exceptions often deserve different responses: maybe you create a default config if the file is missing, but you definitely want to alert someone if permissions are broken.


Network and API calls: best examples from 2024–2025 Ruby apps

Modern Ruby apps spend a lot of time talking to APIs. Network calls are some of the most useful examples of Ruby exception handling examples, because they show how to mix retries, logging, and fallbacks.

Here’s a pattern using Net::HTTP and a basic retry loop:

require "net/http"
require "uri"

uri = URI("https://api.example.com/data")
max_retries = 3
attempt = 0

begin
  attempt += 1
  response = Net::HTTP.get_response(uri)

  case response
  when Net::HTTPSuccess
    puts "Got data: #{response.body[0..50]}..."
  when Net::HTTPTooManyRequests
    warn "Rate limited; status: #{response.code}"
  else
    warn "Unexpected response: #{response.code} #{response.message}"
  end
rescue SocketError, Errno::ECONNREFUSED, Net::OpenTimeout, Net::ReadTimeout => e
  warn "Network error (attempt #{attempt}): #{e.class} - #{e.message}"
  retry if attempt < max_retries
  warn "Giving up after #{attempt} attempts"
end

In 2024–2025, this sort of code usually sits behind a higher‑level HTTP client (Faraday, HTTParty, or a dedicated SDK). The pattern stays the same, though: rescue concrete network exceptions, log them, and decide when to retry versus fail fast. These are the real examples that separate a hobby script from a production‑ready service.


Scoped rescue as a modifier: tiny examples include one‑liners

Ruby lets you attach rescue directly to a single expression. It’s handy, but often abused. Here’s a small example of Ruby exception handling using the modifier form without hiding real bugs:

## Bad: hides every kind of StandardError
user = User.find_by(id: params[:id]) rescue nil

## Better: rescue just what you expect
user = begin
  User.find(params[:id])
rescue ActiveRecord::RecordNotFound
  nil
end

Or, for a very narrow case where you only care about one known failure:

value = Integer(params[:limit]) rescue 10  # only if you're sure

These examples of Ruby exception handling examples show why clarity beats cleverness. If someone new to the codebase can’t tell what you’re rescuing or why, the one‑liner probably needs to be expanded.


Examples of Ruby exception handling examples in Rails controllers

Rails controllers are a gold mine for practical patterns. One common example of controller‑level error handling is mapping exceptions to HTTP responses.

class ArticlesController < ApplicationController
  rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
  rescue_from ActionController::ParameterMissing, with: :render_bad_request

  def show
    article = Article.find(params[:id])
    render json: article
  end

  def create
    attrs = params.require(:article).permit(:title, :body)
    article = Article.create!(attrs)
    render json: article, status: :created
  end

  private

  def render_not_found(error)
    render json: { error: error.message }, status: :not_found
  end

  def render_bad_request(error)
    render json: { error: error.message }, status: :bad_request
  end
end

This is one of the best examples of Ruby exception handling examples in a web context:

  • rescue_from centralizes behavior instead of repeating begin/rescue blocks.
  • Different exceptions map cleanly to different HTTP status codes.
  • The controller stays focused on happy‑path logic.

In 2024–2025, you’ll also see teams pair patterns like this with error tracking tools such as Sentry or Honeybadger, so unhandled exceptions still get captured and triaged.


Background jobs: retrying with Sidekiq and other workers

Background job systems are another area where examples include nuanced exception handling. For Sidekiq, the default behavior already retries jobs on most errors, but you can customize it.

class SyncUserJob
  include Sidekiq::Job

  sidekiq_options retry: 5

  def perform(user_id)
    user = User.find(user_id)
    ExternalCrm.sync!(user)  # may raise network or API errors
  rescue ActiveRecord::RecordNotFound => e
#    # No point retrying if the user is gone
    Sidekiq.logger.warn("User not found: #{user_id} - #{e.message}")
  end
end

This example of a background job focuses on a simple rule: retry transient failures, don’t retry permanent ones. By rescuing ActiveRecord::RecordNotFound inside the job, you prevent Sidekiq from hammering an already‑deleted record.

For more advanced setups, teams often combine this with circuit‑breaker patterns or rate‑limit checks, especially when talking to third‑party APIs that enforce strict quotas.


Using ensure and else: real examples you should actually use

Many developers ignore else and misuse ensure. Here’s a practical pattern that makes both work for you.

log = Logger.new($stdout)

begin
  log.info "Opening connection..."
  conn = Database.connect

  result = conn.query("SELECT * FROM users")

#  # `else` only runs if no exception was raised
#  # so it's a nice place for success‑only logic
  puts "Fetched #{result.count} users"
rescue Database::ConnectionError => e
  log.error "DB connection failed: #{e.message}"
rescue Database::QueryError => e
  log.error "Query failed: #{e.message}"
else
  log.info "Query completed successfully"
ensure
#  # `ensure` runs no matter what
  conn&.close
  log.info "Connection closed"
end

These examples of Ruby exception handling examples highlight a good rule of thumb:

  • Use else for code that should run only when everything succeeds.
  • Use ensure for cleanup that must run even when things go wrong.

This pattern mirrors resource‑safety ideas you’ll see in other languages, and it lines up with general reliability advice from software engineering programs at universities like Harvard, which emphasize cleaning up resources predictably.


Defining custom exceptions: examples include domain‑specific errors

At some point, StandardError subclasses from the standard library won’t be enough. Real examples in production apps usually define their own exception hierarchy.

module Payments
  class Error < StandardError; end
  class CardDeclined < Error; end
  class FraudSuspected < Error; end
  class GatewayUnavailable < Error; end
end

class PaymentProcessor
  def charge!(user, amount_cents)
    response = gateway.charge(user.card_token, amount_cents)

    case response.code
    when "card_declined"
      raise Payments::CardDeclined, response.message
    when "fraud_suspected"
      raise Payments::FraudSuspected, response.message
    when "gateway_unavailable"
      raise Payments::GatewayUnavailable, "Payment gateway offline"
    end

    response
  end

  private

  def gateway
    @gateway ||= ThirdPartyGateway.new
  end
end

Then, in a controller or service object, you can handle these in a focused way:

begin
  PaymentProcessor.new.charge!(current_user, 5000)
rescue Payments::CardDeclined => e
  flash[:alert] = "Your card was declined: #{e.message}"
rescue Payments::FraudSuspected => e
  flash[:alert] = "We detected a problem with this payment. Please contact support."
rescue Payments::GatewayUnavailable => e
  flash[:alert] = "Payments are temporarily unavailable. Please try again later."
end

This is one of the best examples of Ruby exception handling examples for domain logic: the exceptions express business meaning, not just low‑level technical failure.


In 2024 and 2025, exception handling in Ruby isn’t just about rescue blocks; it’s about observability. Teams want to know when exceptions happen, where, and how often.

Some patterns you’ll see:

  • Structured logging (JSON logs with fields like error_class, error_message, user_id).
  • Correlating exceptions with trace IDs in systems like OpenTelemetry.
  • Using error budgets and SLOs, often inspired by reliability guidance from organizations like NIST and major engineering programs.

Here’s a small example of Ruby exception handling examples wired into structured logging:

require "logger"
require "json"

class JsonLogger
  def initialize(io = $stdout)
    @logger = Logger.new(io)
  end

  def error(message, **context)
    payload = { level: "ERROR", message:, **context }
    @logger.error(payload.to_json)
  end
end

logger = JsonLogger.new

begin
  risky_operation
rescue => e
  logger.error(
    "Operation failed",
    error_class: e.class.name,
    error_message: e.message,
    backtrace: e.backtrace&.first(5)
  )
  raise  # re‑raise so upstream still sees the failure
end

In other words, modern examples include not just catching the error, but also making sure future you can see what happened without SSHing into a server and tailing plain‑text logs.


Common mistakes in Ruby exception handling (and better examples)

You can learn a lot from bad patterns. Here are a few anti‑patterns, followed by better examples of Ruby exception handling examples.

Catching everything and doing nothing

begin
  do_something_risky
rescue
#  # ignore
end

Better:

begin
  do_something_risky
rescue SpecificError => e
  warn "Handled specific failure: #{e.message}"
end

Rescuing too broadly in libraries

Library code that rescues StandardError and then returns nil is a debugging nightmare. Instead, libraries should raise meaningful exceptions and let the application decide how to handle them.

Swallowing interrupts

begin
  long_running_task
rescue Exception
#  # this will catch Interrupt and SystemExit too — bad idea
end

Better:

begin
  long_running_task
rescue StandardError => e
#  # handle normal errors
  warn "Task failed: #{e.message}"
end

Or, if you truly need to intercept interrupts, re‑raise them after cleanup.

These real examples help keep exception handling honest and prevent the “it just silently stopped working” bug class that everyone hates.


FAQ: short answers with real examples

Q: Can you show a simple example of Ruby exception handling in a loop?

Yes. Here’s a small pattern for retrying user input:

3.times do
  print "Enter an integer: "
  begin
    value = Integer(gets.chomp)
    puts "Thanks, you entered #{value}"
    break
  rescue ArgumentError
    puts "That wasn't an integer. Try again."
  end
end

Q: When should I define custom exception classes?

Define them when you need to express domain‑specific failures. For instance, Payments::CardDeclined or Inventory::OutOfStock give you clear, focused examples of Ruby exception handling examples that map directly to business rules.

Q: Is rescue nil ever okay?

It’s acceptable only when you are absolutely sure about the exception type and are intentionally ignoring it, such as optional logging or non‑critical metrics. In most cases, a clearer example of Ruby exception handling is to rescue a specific error and document why you’re ignoring it.

Q: How do I test exception handling in Ruby?

In RSpec, you can use expect { ... }.to raise_error(SomeError) for raising, and you can also assert on side effects (like logging or return values) when you rescue. Writing tests around these examples of Ruby exception handling examples keeps your error paths from rotting over time.


The bottom line: the best examples of Ruby exception handling examples are specific, honest about failure, and wired into logging and monitoring. If your rescue blocks make it easier to debug problems six months from now, you’re doing it right.

Explore More Ruby Code Snippets

Discover more examples and insights in this category.

View All Ruby Code Snippets