Exception Handling in Dart
By Syncfusion®, Inc. · Last updated
How to catch errors with try, on, catch and finally, throw and rethrow them, write your own exceptions, and read Dart's built-in errors.
What Is an Exception?
Exception = a problem that happens while the program runs, and stops the normal flow.
- A user types
abcwhere a number was expected. - A file the program needs is missing.
- A list is empty, but the code asks for its first item.
- If nobody handles the problem, the program crashes and prints
Unhandled exception.
Simple lesson: an exception is a "something went wrong" signal, and you can catch it so the program keeps going.
A Real-World Example
Think of a school bus trip.
- The plan: go to the museum, see the exhibits, come back.
- Problem: a tyre goes flat on the way. That is the exception.
- The driver does not give up the whole trip. He has a plan: change the tyre, or call another bus. That is handling.
- And whatever happens, everyone does a head count at the end. That is
finally.
Choose what a student typed, and watch which blocks run:
Every Run box on this page runs on the course's Dart runner in your browser.
Some boxes fail on purpose, to show the error Dart prints. They are marked as expected errors.
What You Will Learn
In this lesson, you will learn:
- The kinds of mistakes in a program, and how errors differ from exceptions.
- How
try,on,catchandfinallywork, and in what order handlers are tried. - How to read a stack trace.
- How to
throw,rethrow, and make your own exceptions. - The common built-in errors and exceptions.
Kinds of Problems
Syntax, Runtime and Logic Errors
| Kind | When it is found | Example | Who catches it |
|---|---|---|---|
| Syntax / compile error | before running | a missing ; | Dart, before the program starts |
| Runtime error | while running | int.parse('abc') | your try and catch |
| Logic error | never, by Dart | total = a - b when you meant + | only testing |
try and catch handle runtime problems. They cannot fix wrong logic or wrong syntax.
An unhandled exception crashes the program
void main() {
print('Reading marks...');
var marks = int.parse('ninety');
print('Marks: $marks');
}
The last line never runs. The program stopped at int.parse.
Errors vs Exceptions
Exception | Error | |
|---|---|---|
| Means | an expected problem from outside | a bug in the code |
| Example | bad user input, missing file | index out of range, null where not allowed |
| Should you catch it? | yes, and recover | usually no: fix the code |
| Built-in examples | FormatException, TimeoutException | RangeError, StateError, TypeError |
Simple lesson: catch exceptions to recover, and fix the code that causes errors.
try, on, catch and finally
The Basic Shape
try {
// code that might fail
} on SomeException {
// runs for that type
} catch (e) {
// runs for anything else, e is the problem
} finally {
// runs every time
}
Reading marks safely
void main() {
for (var text in ['85', 'abc']) {
try {
var marks = int.parse(text);
print('Marks: $marks');
} on FormatException {
print('"$text" is not a number');
} finally {
print('Checked "$text"');
}
}
print('The program is still running.');
}
on, catch and Both
on Type: catch only this type. You do not get the object.catch (e): catch anything.eis the thrown object.on Type catch (e): catch only this type, and get the object.
on Type catch (e) gives you the details
void main() {
try {
int.parse('12a');
} on FormatException catch (e) {
print('Problem: ${e.message}');
print('Type: ${e.runtimeType}');
}
}
Matching Order
Handlers are tried from top to bottom. The first one that fits runs. The rest are skipped.
Which handler catches it?
Put specific types first and a general catch (e) last.
If catch (e) comes first, it catches everything, and the specific handlers below it never run.
finally
finally = a block that runs every time: after success, after a caught error, and even when the error is not caught.
- Use it for clean-up: close a file, stop a timer, print "done".
Simple lesson: try the risky code, on/catch the problems you expect, and clean up in finally.
Stack Traces
Stack trace = the list of function calls that were running when the problem happened. It shows where it went wrong.
- Get it as the second name in
catch:catch (e, s). - Read it from the top: the top line is where the error was thrown.
void main() {
try {
loadMarks();
} catch (e, s) {
print(e);
print(s);
}
}
// FormatException: Invalid radix-10 number (at character 1)
// #0 int.parse (dart:core-patch/integers_patch.dart:52:7)
// #1 loadMarks (file:///school/bin/main.dart:12:3)
// #2 main (file:///school/bin/main.dart:3:5)
- Line
#1says the problem came fromloadMarks, line 12. - The course runner prints a short stack trace. Your computer prints the full one.
Simple lesson: a stack trace is a trail of footprints that leads you to the line that failed.
throw and rethrow
Throwing Your Own Problem
throw = signal a problem yourself. Usually you throw an Exception or an Error object.
Throwing when the input is wrong
int checkMarks(int marks) {
if (marks < 0 || marks > 100) {
throw ArgumentError('Marks must be 0 to 100, not $marks');
}
return marks;
}
void main() {
try {
print(checkMarks(88));
print(checkMarks(140));
} on ArgumentError catch (e) {
print(e.message);
}
}
Propagation
Propagation = an uncaught exception rises up out of each function, to the one that called it, until a catch stops it.
The problem rises from parse to main
int parse(String text) => int.parse(text);
int loadMarks(String text) {
print('loadMarks starts');
var m = parse(text);
print('loadMarks ends');
return m;
}
void main() {
try {
loadMarks('x9');
} on FormatException {
print('main caught it');
}
}
loadMarks ends never prints. The exception jumped straight out of loadMarks.
rethrow
rethrow = inside a catch, pass the same problem on to the caller. Useful when you want to note it, but not handle it here.
Note it, then pass it on
void saveMarks(String text) {
try {
int.parse(text);
} catch (e) {
print('saveMarks: could not save "$text"');
rethrow;
}
}
void main() {
try {
saveMarks('ten');
} on FormatException catch (e) {
print('main: ${e.message}');
}
}
Simple lesson: throw starts a problem, it rises until caught, and rethrow sends a caught one further up.
Custom Exceptions
Custom exception = your own exception class. Make it implements Exception.
A fee exception with details
class FeeNotPaidException implements Exception {
final String student;
final int due;
FeeNotPaidException(this.student, this.due);
@override
String toString() => 'FeeNotPaidException: $student owes Rs. $due';
}
void issueHallTicket(String name, int due) {
if (due > 0) {
throw FeeNotPaidException(name, due);
}
print('Hall ticket issued to $name');
}
void main() {
for (var (name, due) in [('Asha', 0), ('Ravi', 1500)]) {
try {
issueHallTicket(name, due);
} on FeeNotPaidException catch (e) {
print(e);
print('Please pay Rs. ${e.due} at the office.');
}
}
}
- A custom type lets callers catch exactly this problem with
on FeeNotPaidException. - Fields like
duecarry the details. - Override
toString()so the message is clear.
Nested Handlers
A try can sit inside another. The inner one gets the first chance:
Inner handler first, outer handler for the rest
void main() {
try {
try {
var data = [1, 2];
print(data[9]);
} on FormatException {
print('inner: format problem');
} finally {
print('inner finally');
}
} on RangeError {
print('outer: position problem');
}
}
The inner try had no handler for RangeError, so it rose to the outer one. The inner finally still ran first.
Simple lesson: make your own exception classes so callers can catch exactly the problem they understand.
Built-in Errors and Exceptions
| Name | Thrown when | Example |
|---|---|---|
FormatException | text is in the wrong format | int.parse('abc') |
ArgumentError | a function gets a bad argument | your own throw ArgumentError(...) |
RangeError | an index or value is out of range | [1, 2][5] |
StateError | the object is in the wrong state | [].first |
UnsupportedError | the operation is not allowed | adding to a const list |
UnimplementedError | a method is not written yet | throw UnimplementedError(); |
TypeError | a value has the wrong type, or ! on null | String? s; s!.length |
NoSuchMethodError | a missing method is called on a dynamic value | dynamic d = 5; d.fly(); |
AssertionError | an assert fails (when asserts are on) | assert(age > 0) |
ConcurrentModificationError | a list changes while a loop reads it | for (x in l) l.add(x) |
Meeting a few built-in errors
void main() {
try {
var empty = <int>[];
print(empty.first);
} on StateError catch (e) {
print('StateError: ${e.message}');
}
try {
const houses = ['Red', 'Blue'];
houses.add('Pink');
} on UnsupportedError catch (e) {
print('UnsupportedError: ${e.message}');
}
try {
var marks = [80, 90];
for (var m in marks) {
marks.add(m + 1);
}
} on ConcurrentModificationError {
print('ConcurrentModificationError: do not change a list while looping over it');
}
}
Easy way to remember
- try: "let me try this".
- on Type: "if it is this problem...".
- catch (e, s): "...give me the problem and where it happened".
- finally: "no matter what, do this".
- throw / rethrow: "raise a problem / pass it on".
Simple lesson: the built-in names tell you what went wrong, so read the first word of every error.
TL;DR
- An exception is a runtime problem. Unhandled, it crashes the program.
tryrisky code.on Type,catch (e, s)handle it.finallyalways runs.- Handlers are tried top to bottom. Specific first, general
catchlast. throwraises a problem. It propagates up until caught.rethrowpasses it on.- Custom exceptions:
class X implements Exception. CatchExceptions, fixErrors.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
catch (e) before on FormatException | Order does not matter | The first match wins | The specific handler never runs. |
An empty catch (e) {} | Hiding errors is safe | At least print or log it | Bugs disappear silently. |
Catching Errors to hide bugs | All problems should be caught | Fix the code that causes errors | The bug stays in the program. |
throw e; inside catch | Same as rethrow | rethrow keeps the original stack trace | The trail to the real line is lost. |
| A custom exception that extends nothing | Any class can be thrown | implements Exception makes intent clear | on Exception will not catch it. |
| Expecting code after a throw to run | Dart finishes the block | Control jumps to the handler | Output you expected never appears. |
Check Yourself
Exercises
What to Remember
- An exception is a runtime problem. Unhandled, it stops the program.
trythe risky code, handle it withonandcatch, and clean up infinally.- Handlers are tried top to bottom, so put specific types first.
throwraises a problem, it rises until caught, andrethrowpasses it on.- Make custom exceptions with
implements Exception, and fix the code behindErrors.
Next: Async Basics and Futures. You will run slow work without freezing your program.