Custom Exception Classes
A custom exception class is your own error type — usually a subclass of StandardError — that gives failures a meaningful name and can carry extra data for the code that rescues it.
Learn Custom Exception Classes in our free Ruby course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Ruby course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
You'll define error classes, raise and rescue them by type, attach attributes, and use retry and ensure .
What You'll Learn in This Lesson
1️⃣ Defining and Raising a Custom Error
Create an error type by subclassing StandardError — often that's the entire class body. Then raise YourError, "message" to signal it, and rescue YourError => e to catch exactly that type while letting unrelated errors pass through.
2️⃣ Attributes, Messages & Rescuing by Type
Override initialize to store extra data and call super(message) so the message still works. Expose data with attr_reader , then read it in the handler. List multiple rescue clauses — most specific first — to handle each error type differently.
3️⃣ Recovering with retry and ensure
retry inside a rescue re-runs the begin block — ideal for transient failures, as long as you cap the attempts. ensure runs whether or not an exception happened, making it the right home for cleanup.
Your turn. Finish a tiny bank withdrawal that raises a custom error when funds are short. Fill in each ___ .
Mini-Challenge: Email Registration
Build an InvalidEmailError that stores the offending address, then process a list so one bad email doesn't halt the rest. This per-item rescue pattern is everywhere in real systems. Run with ruby register.rb .
Common Errors (and the fix)
- Inheriting from Exception — ❌ A bare rescue won't catch it. ✅ Inherit from StandardError .
- Forgetting super — ❌ A custom initialize without super loses the message. ✅ Call super(message) .
- Broad rescue first — ❌ A rescue StandardError placed before specific ones shadows them. ✅ Order specific → general.
- retry with no limit — ❌ Endless retry can loop forever. ✅ Cap attempts with a counter.
Pro Tips
- 💡 Group errors: create a base AppError < StandardError and subclass it, so one rescue AppError catches the whole family.
- 💡 Method-level rescue: you can put rescue / ensure in a method body without an explicit begin .
- 💡 Re-raise: a bare raise inside a rescue re-raises the current exception after logging.
📋 Quick Reference — Exceptions
Concept
Ruby Syntax
Define
class MyError < StandardError
Raise
raise MyError, "msg"
Rescue type
rescue MyError => e
Read message
e.message
Retry
retry
Always run
ensure ... end
Frequently Asked Questions
🎉 Lesson Complete!
- ✅ Define errors as subclasses of StandardError
- ✅ raise to signal, rescue Type => e to catch by type
- ✅ Carry extra data with attributes + super(message)
- ✅ retry re-runs the block; cap your attempts
- ✅ ensure always runs — perfect for cleanup
- ✅ Next lesson: Dates & Times
Practice quiz
Which class should a custom exception usually inherit from?
- Exception
- RuntimeError only
- StandardError
- Object
Answer: StandardError. Inherit from StandardError so a bare rescue catches it, without swallowing system-level signals.
Why NOT inherit directly from Exception?
- A bare rescue won't catch it, and you risk swallowing signals like Ctrl-C
- It is slower
- It can't store a message
- It is deprecated
Answer: A bare rescue won't catch it, and you risk swallowing signals like Ctrl-C. A bare rescue catches StandardError, not all of Exception. Inheriting from Exception means rescue misses it and may swallow SignalInterrupt or SystemExit.
How do you attach extra data, like a status code, to a custom exception?
- You cannot
- Use a global variable
- Pass it to rescue
- Define initialize, store it in an ivar, expose with attr_reader, and call super(message)
Answer: Define initialize, store it in an ivar, expose with attr_reader, and call super(message). Override initialize to store the data, expose it with attr_reader, and call super(message) so the message machinery still works.
What does do inside a rescue clause?
- Re-raises the error
- Jumps back to the start of the begin block
- Skips to ensure
- Exits the program
Answer: Jumps back to the start of the begin block. retry restarts the begin block — useful for transient failures, as long as you cap attempts.
When does the clause run?
- No matter what — success or failure
- Only on success
- Only on error
- Only when retry is used
Answer: No matter what — success or failure. ensure always runs, making it the right home for cleanup like closing files.
When listing multiple rescue clauses, which order is correct?
- Most general first
- Alphabetical
- Most specific first, then general
- Order doesn't matter
Answer: Most specific first, then general. Ruby checks rescues top to bottom; a broad StandardError first would shadow specific ones, so put specific first.
In a custom initialize, what happens if you forget to call ?
- The class won't load
- The standard message machinery is lost
- retry stops working
- ensure runs twice
Answer: The standard message machinery is lost. Without super(message), the exception's message handling is lost.
What does do?
- Defines the error class
- Rescues the error
- Returns nil
- Signals the error with that message
Answer: Signals the error with that message. raise signals the exception and creates the object with the given message.
To catch a whole family of your app's errors with one rescue, you should...
- Rescue Exception
- Create a base AppError < StandardError and subclass it
- Use retry
- Rescue NilClass
Answer: Create a base AppError < StandardError and subclass it. A base AppError < StandardError lets one catch every subclass in the family.
What does an uncapped risk?
- A syntax error
- Swallowing the message
- Looping forever on a persistent error
- Running ensure twice
Answer: Looping forever on a persistent error. Without an attempt counter, retry can loop forever if the error keeps recurring.
Continue this course
- Previous: Pattern Matching (case/in)
- Next: Dates & Times