Error Handling (tryCatch)
Reviewed & published by Brayan K
Error handling is how you signal problems with stop(), warning(), and message(), and how you catch them with tryCatch() so a failure produces a graceful fallback instead of crashing your program.
Part of the free R course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
You'll learn to raise the three R conditions, catch them with tryCatch error/warning/finally handlers, read the message with conditionMessage(), use the lightweight try(), and validate inputs early with stopifnot().
What You'll Learn in This Lesson
- Raising conditions with stop(), warning(), message()
- Catching them with tryCatch() handlers
- The finally block for guaranteed cleanup
- Reading the text with conditionMessage()
- The lightweight try() and try-error objects
- Validating inputs early with stopifnot()
1️⃣ Signalling: stop, warning, message
R has three condition types you can raise yourself. stop() is a fatal error that halts execution. warning() flags something suspicious but lets the code continue. message() prints an informational note. Choosing the right one makes your functions communicate clearly.
2️⃣ Catching with tryCatch()
tryCatch() runs an expression and, if a condition fires, hands it to the matching handler — error =, warning =, or message = — each a function receiving the condition object. conditionMessage() extracts its human-readable text. The handler's return value becomes the result.
3️⃣ The finally Block
A finally = block runs after the expression and any handler, whether the call succeeded or failed. It's where you put guaranteed cleanup — closing a file, releasing a connection, or simply logging that an attempt finished.
# finally = runs no matter what — perfect for cleanup.
read_safely <- function(x) {
tryCatch(
{
if (!is.numeric(x)) stop("need a number")
x * 2
},
error = function(e) { message("error: ", conditionMessage(e)); -1 },
finally = message("done attempting") # always runs
)
}
read_safely(5) # done attempting / -> [1] 10
read_safely("a") # error: need a number / done attempting / -> [1] -14️⃣ try() and stopifnot()
try() is the lightweight option: it prints an error but lets your script keep going, returning a "try-error" object you can test. stopifnot() is a one-line input guard — it errors unless every condition is TRUE, naming the one that failed.
# try() is the lightweight version: it prints the error but does NOT
# stop the surrounding script, returning a try-error object instead.
result <- try(log("oops"), silent = TRUE)
class(result) # -> [1] "try-error"
# stopifnot() is a concise guard: it errors UNLESS every test is TRUE.
checked_mean <- function(x) {
stopifnot(is.numeric(x), length(x) > 0) # validate inputs up front
mean(x)
}
checked_mean(c(2, 4, 6)) # passes -> [1] 4
# checked_mean("hi") would error: is.numeric(x) is not TRUEYour turn. Fill in the ___ blank, run it, and compare with the expected output.
Mini-Challenge: A Robust Parser
This ties everything together: validate with stopifnot(), raise your own stop(), catch it with tryCatch(), and clean up with finally — the shape of every robust function.
# 🎯 MINI-CHALLENGE: a robust parser.
#
# Write parse_num(x) that:
# 1. Uses stopifnot(length(x) == 1) to require a single value.
# 2. Inside tryCatch, does as.numeric(x); if the result is NA,
# call stop("not a number").
# 3. Catches the error and returns 0 instead.
# 4. Uses finally = to message("parse attempt finished").
#
# Then test: parse_num("42") and parse_num("abc")
#
# ✅ Example output:
# parse attempt finished
# [1] 42
# parse attempt finished
# [1] 0
# your code hereCommon Errors (and the fix)
- ❌ Writing error = function(e) ... but forgetting it must return a fallback. ✅ The handler's value becomes the result — return something sensible like NA.
- ❌ Using stop() where a warning() fits (or vice versa). ✅ stop() halts; warning() continues.
- ❌ Expecting try() to give you a clean value on error. ✅ It returns a "try-error" — check with inherits(x, "try-error").
- ❌ Catching errors too broadly and hiding real bugs. ✅ Handle only the conditions you expect; let genuine bugs surface.
Pro Tips
- 💡 suppressWarnings() / suppressMessages() silence noise when you genuinely expect it.
- 💡 conditionMessage(e) gives the text; conditionCall(e) gives the call that raised it.
- 💡 Validate at the top: a few stopifnot() lines make failures obvious and early.
📋 Quick Reference — Error Handling
| Task | R Syntax |
|---|---|
| Raise a fatal error | stop("msg") |
| Raise a warning | warning("msg") |
| Catch an error | tryCatch(expr, error = function(e) ...) |
| Always run cleanup | tryCatch(expr, finally = ...) |
| Read the message | conditionMessage(e) |
| Validate inputs | stopifnot(is.numeric(x)) |
🎉 Lesson Complete!
- ✅ stop() errors, warning() warns, message() informs
- ✅ tryCatch() catches conditions with named handlers and a fallback value
- ✅ finally = always runs — use it for cleanup
- ✅ try() is lightweight; stopifnot() validates inputs up front
Practice quiz
Which function raises a fatal ERROR that halts execution?
- message()
- stop()
- warning()
- cat()
Answer: stop(). stop() signals an error and aborts the call unless it is caught.
Which function signals a problem but lets execution CONTINUE?
- warning()
- stop()
- abort()
- halt()
Answer: warning(). warning() flags something suspicious but does not stop the code.
Which function prints an informational note to the console (stderr)?
- stop()
- warning()
- print()
- message()
Answer: message(). message() emits an informational note without implying an error.
What does tryCatch() do when a matching condition fires?
- Ignores it silently
- Runs the matching handler and returns its value
- Restarts R
- Prints the call stack and quits
Answer: Runs the matching handler and returns its value. tryCatch() stops evaluating the expression and runs the matching handler, returning what it returns.
Which tryCatch() argument always runs, whether or not an error occurred?
- always =
- ensure =
- finally =
- cleanup =
Answer: finally =. finally = runs at the end no matter what, ideal for cleanup.
Which function extracts the human-readable text from a condition object e?
- conditionMessage(e)
- message(e)
- errorText(e)
- e$text
Answer: conditionMessage(e). conditionMessage(e) returns the condition's message string.
What does try() return when its expression errors?
- NULL
- A warning object
- An object of class "try-error"
- The number 0
Answer: An object of class "try-error". try() prints the error but returns a "try-error" object so the script continues.
What does stopifnot(is.numeric(x), length(x) > 0) do?
- Always returns TRUE
- Prints x
- Loops over x
- Errors unless every condition is TRUE
Answer: Errors unless every condition is TRUE. stopifnot() throws an informative error the moment any condition is not entirely TRUE.
In tryCatch(), how is an error handler specified?
- error = function(e) { ... }
- onError: { ... }
- catch(e) { ... }
- except e: ...
Answer: error = function(e) { ... }. Handlers are named arguments like error = function(e) ... and warning = function(w) ...
In tryCatch(), what becomes the overall result when a handler runs?
- Always NA
- Always NULL
- The handler's return value
- The original error object
Answer: The handler's return value. The matching handler's return value becomes the result of the tryCatch() call.
Continue this course
- Previous: Frequency Tables (table, prop.table)
- Next: Environments & Scoping — Lexical scoping, the global environment, and the <<- super-assignment
- Quick reference: R cheat sheet
Frequently asked questions
What is the difference between stop(), warning(), and message()?
These signal the three condition types. stop() raises an error: it immediately halts execution and, unless caught, prints 'Error: ...' and aborts the call. warning() raises a warning: execution continues and the warning is shown (often collected and printed at the end). message() emits an informational note to the console (technically to stderr) without implying anything is wrong — use it for progress updates. Errors are for 'I cannot continue', warnings for 'this is suspicious but I carried on', and messages for 'here is what I'm doing'.
How does tryCatch() work?
tryCatch() evaluates its first argument (the expression) and watches for conditions. You supply named handler functions — error =, warning =, message = — and if a matching condition is signalled, R stops evaluating the expression and runs that handler instead, returning whatever the handler returns. This means a caught error no longer crashes your program; you decide the fallback. The optional finally = block runs at the very end whether or not a condition occurred, which is ideal for cleanup like closing files or connections.
When should I use try() instead of tryCatch()?
try() is a simpler, older tool: it runs an expression and, if it errors, prints the message but lets the surrounding code continue, returning an object of class 'try-error' you can test for. Use it for quick, one-off protection when you do not need different behaviour for warnings versus errors. tryCatch() is more powerful and explicit — it lets you handle each condition type separately, return custom fallback values, and run finally cleanup — so prefer it for anything beyond the most casual case.
What is stopifnot() for?
stopifnot() is a compact way to validate assumptions at the top of a function. You pass it one or more logical expressions, and it throws an informative error the moment any of them is not entirely TRUE, naming the failing test. So stopifnot(is.numeric(x), length(x) > 0) guarantees x is a non-empty numeric vector before the rest of the function runs. It encourages 'fail fast' programming: catch bad inputs immediately with a clear message rather than letting them cause a confusing error deep inside your code.