Dart (3.13) Module 10 – Errors and exceptions
Dart exceptions and errors: try, catch, finally
Throw and catch Dart exceptions with try, on, catch and finally, tell an Exception from an Error, keep stack traces and write custom exceptions.
What you will learn
- Catch the failures you expect with try, on, catch and finally
- Distinguish an Exception, which a program handles, from an Error, which reports a bug
- Read a stack trace and keep it intact when you rethrow or wrap an exception
- Write a custom exception that names the field, the input and the reason
Before you start
What you’ll learn · Before you start
What you will learn
- Catch the failures you expect with try, on, catch and finally
- Distinguish an Exception, which a program handles, from an Error, which reports a bug
- Read a stack trace and keep it intact when you rethrow or wrap an exception
- Write a custom exception that names the field, the input and the reason
Before you start
On this page
An order file of 1,200 lines arrives with 12a typed in the quantity column of line 37. One importer stops at that
line with a stack trace; a second skips it, says why and imports the other 1,199; a third imports everything with a
wrong total and no message at all. The difference between them is a few lines of try, on, catch and finally.
This lesson follows one failure from the line that throws it to the line that handles it, in programs that ran on
Dart 3.13.5. You will catch only the failures you expect, tell an Exception from an Error, read a stack trace and
keep it when you pass a failure on, and write an exception that says exactly what was wrong.
A throw leaves every function until a catch takes it
Here is an importer with no error handling at all. Its second line says pads,12a:
// An order importer with no error handling. The second line's quantity is not a number.
const lines = ['pens,3', 'pads,12a', 'clips,5'];
int parseQuantity(String text) => int.parse(text);
int totalItems(List<String> lines) {
var total = 0;
for (final line in lines) {
final [item, qty] = line.split(',');
total += parseQuantity(qty);
print('$item: $qty');
}
return total;
}
void main() {
print('total: ${totalItems(lines)}');
} Output (exit status 255)
pens: 3
Printed as an error (standard error)
Unhandled exception: FormatException: Invalid radix-10 number (at character 1) 12a ^ #0 int._handleFormatError (dart:core-patch/integers_patch.dart:150:5) #1 int._parseRadix (dart:core-patch/integers_patch.dart:179:14) #2 int._parse (dart:core-patch/integers_patch.dart:121:12) #3 int.parse (dart:core-patch/integers_patch.dart:81:12) #4 parseQuantity (crash.dart:4:39) #5 totalItems (crash.dart:10:14) #6 main (crash.dart:17:19) #7 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:313:19) #8 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run crash.dart
The program printed pens: 3 and then stopped. int.parse('12a') could not read a whole number, so it threw an
exception: An object a Dart program throws to report a failure it is expected to handle, such as text that does not parse or a file that is missing. Classes that implement Exception can be caught by type with an on clause., a FormatException. Nothing caught it, so it became an
unhandled exception: An exception that no catch clause took. A Dart command line program then prints Unhandled exception, the message and the stack trace on standard error, and exits with code 255.: the runtime printed Unhandled exception:, the message with
a caret under the character it could not read, and a stack trace: The list of calls that were running when a value was thrown, newest first, each with its file, line and column. Dart prints it for an unhandled exception and hands it to catch (e, s) as a StackTrace., all on
standard error: A program's second output stream, meant for problems and diagnostics; > redirects standard output only, so standard error stays on the terminal. More in “Your first Dart program and project layout”. Then it ended the program with the
exit status: The number a program hands to the shell when it ends: 0 for success, anything else for a problem. Dart sets it from exitCode, or from the value given to exit(). More in “Your first Dart program and project layout” 255, which the dart command uses for a runtime error (254 is its code for a
compile-time error, the dart:io documentation says).
Read the trace from the top: frames #0 to #3 are inside int.parse, then come the program’s own frames,
parseQuantity (crash.dart:4:39), totalItems (crash.dart:10:14) and main (crash.dart:17:19): file, line and
column of each call on the way to the throw. Step through the same run and watch the throw leave each function:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- main calls totalItems. main calls totalItems with the three order lines. Each call puts a frame on the call stack, which records the function that runs and where to go back to when it returns.
- Line 1 adds 3. For pens,3 the function parseQuantity returns 3, totalItems adds it to total and prints pens, 3. Nothing has been thrown yet, so every call returns normally.
- Line 2 reaches int.parse. For pads,12a totalItems calls parseQuantity, which calls int.parse with the text 12a. Three frames of this program are now on the stack, with int.parse on top.
- int.parse throws. 12a is not a whole number, so int.parse throws a FormatException. No later line of int.parse runs. The runtime now looks for a catch, starting with the function that threw.
- No catch in parseQuantity. parseQuantity has no try, so the exception leaves it at once, with no return value. Its frame comes off the stack.
- No catch in totalItems. totalItems has no try either. It leaves in the middle of line 2, so clips,5 is never read and total is never returned or printed.
- No catch in main. main has no try, so the exception leaves main as well. With no frame of the program left to catch it, the runtime takes it.
- The program ends with 255. The runtime prints Unhandled exception, the message and the stack trace on standard error, and ends the program with exit code 255. The trace lists the frames from int.parse down to main.
A throw never returns a value. Each function between the throw and a catch stops at the line it was running, which is
why clips,5 was never read and total: was never printed. The search for a catch starts in the function that threw
and moves to its caller, then to that caller’s caller, until it reaches main and the runtime.
Checkpoint
Check: A throw climbs the call stack
on chooses which failures a catch handles
The importer can do better at line 2: report the line and carry on. Put the call that may fail inside try, and add
an on clause for the type of failure you expect:
// The same importer, now handling the one failure it expects: a quantity that is not a whole number.
const lines = ['pens,3', 'pads,12a', 'clips,5', 'tape,-'];
int parseQuantity(String text) => int.parse(text);
void main() {
var total = 0;
var skipped = 0;
for (final (index, line) in lines.indexed) {
final [item, qty] = line.split(',');
try {
total += parseQuantity(qty);
print('line ${index + 1}: $item x $qty');
} on FormatException catch (e) {
skipped++;
print('line ${index + 1}: skipped, "${e.source}" is not a whole number (${e.message})');
}
}
print('total: $total items, $skipped lines skipped');
} Output
line 1: pens x 3 line 2: skipped, "12a" is not a whole number (Invalid radix-10 number) line 3: clips x 5 line 4: skipped, "-" is not a whole number (Invalid number) total: 8 items, 2 lines skipped
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run import_lines.dart
on FormatException catch (e) handles exactly that type and gives the caught object the name e, so the message
can use e.source (the text that failed) and e.message. Both bad lines, 12a and -, were skipped with a reason,
and the total of 8 counts only the lines that were read. on without catch handles the type without naming it, and
catch (e, s) adds the stack trace as s.
A try can have several clauses. Dart tries them from the top and the first one whose type matches the thrown object
handles it, so put specific types before general ones. Four actions go through the same clauses here:
// Four actions, one try statement: the first clause whose type matches the thrown object handles it.
import 'dart:async';
void attempt(String label, void Function() action) {
try {
action();
print('$label: nothing thrown');
} on FormatException catch (e) {
print('$label: on FormatException, ${e.message}');
} on Exception catch (e) {
print('$label: on Exception, a ${e.runtimeType}');
} catch (e) {
print('$label: catch with no on, a ${e.runtimeType}');
}
}
void main() {
attempt('weight "7 kg"', () => int.parse('7 kg'));
attempt('stock lookup', () => throw TimeoutException('stock lookup', const Duration(seconds: 2)));
attempt('empty cart', () => <String>[].first);
attempt('weight "42"', () => int.parse('42'));
} Output
weight "7 kg": on FormatException, Invalid radix-10 number stock lookup: on Exception, a TimeoutException empty cart: catch with no on, a StateError weight "42": nothing thrown
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run which_clause.dart
7 kg matched on FormatException. The TimeoutException is not a FormatException, but it is an Exception, so
the second clause took it. The empty cart threw a StateError, which is neither, and only the last clause, a catch
with no on, takes anything at all. 42 threw nothing, so no clause ran.
A catch with no on around ordinary code
try { … } catch (e) { print('bad input'); } also catches the StateError of the empty cart above, a bug, and
reports it as bad input. Write on FormatException catch (e) for the failure you expect, so a bug still stops the
program where you will see it. Effective Dart lists this as “avoid catches without on clauses”.
Checkpoint
Check: Choosing failures with on and catch
Exception means handle it, Error means fix the code
Dart throws two families of objects. An Exception reports a condition a program can expect and handle: bad input, a
missing file, a slow network. The API documentation says an exception is “intended to be caught” and should carry
useful data. An Error (programming error): An object of the Error class or a subtype, such as StateError, RangeError or TypeError, thrown when code does something its author should have avoided. It reports a bug to fix, not a condition to catch. reports a bug, a mistake the programmer should have
avoided, and the Error class page of the API documentation says a caller should not expect or catch one. The two
families side by side:
| Exception | Error | |
|---|---|---|
| Common types | FormatException, IOException, TimeoutException |
StateError, RangeError, ArgumentError, TypeError, UnsupportedError |
| Thrown when | text cannot be parsed, a file or socket fails, a wait runs out of time | the code asked for something impossible |
| What to do | catch it where the program can recover or report it | fix the code; let it reach the top in normal flow |
These six bugs each throw an Error. The program catches them only to print them, so you can learn their messages:
// Six bugs and the Error each one throws. The catch is here only to print them:
// real code lets an Error reach the top, where it ends the program and points at the bug.
class Cart {
late int itemCount;
}
String kind(Error e) => switch (e) {
RangeError() => 'RangeError',
ArgumentError() => 'ArgumentError',
StateError() => 'StateError',
TypeError() => 'TypeError',
UnsupportedError() => 'UnsupportedError',
_ => 'Error subtype',
};
void show(String bug, Object? Function() code) {
try {
code();
} on Error catch (e) {
print('$bug\n ${kind(e)}: $e');
}
}
void main() {
String? coupon;
final Object id = 'A-17';
show('first item of an empty list', () => <int>[].first);
show('index 3 of a 3-item list', () => [10, 20, 30][3]);
show('! on a null coupon', () => coupon!.length);
show('reading a late field never set', () => Cart().itemCount);
show('casting a String id to int', () => id as int);
show('adding to an unmodifiable list', () => List.unmodifiable([1]).add(2));
} Output
first item of an empty list StateError: Bad state: No element index 3 of a 3-item list RangeError: RangeError (length): Invalid value: Not in inclusive range 0..2: 3 ! on a null coupon TypeError: Null check operator used on a null value reading a late field never set Error subtype: LateInitializationError: Field 'itemCount' has not been initialized. casting a String id to int TypeError: type 'String' is not a subtype of type 'int' in type cast adding to an unmodifiable list UnsupportedError: Unsupported operation: Cannot add to an unmodifiable list
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run messages.dart
Learn to recognise these lines: Bad state: No element is .first on an empty list, RangeError (length) an index
past the end, and Null check operator used on a null value a ! on null (see
sound null safety). LateInitializationError is a
late variable read before it was set, and the cast message names both types.
The late one has no public type of its own: it is an internal subtype of Error, so the switch printed it as
Error subtype.
Key idea
Catch an Exception where your code can do something sensible about it; let an Error reach the top, because its fix is a change to the code, not a catch.
Checkpoint
Check: Exception or Error
finally runs on every way out, except exit()
Some work must happen however a block ends: a file closed, a lock released, a spinner stopped. A
finally clause: The block after try and its catch clauses that runs on every way out of the try: normal completion, a caught exception or one nobody caught. exit() from dart:io skips it. runs after the try block and after any catch that ran, and also
when nothing caught the failure. This program opens two files, and the second one holds 12a:
// finally closes the file whether the sum finishes or a line fails to parse.
import 'dart:convert';
import 'dart:io';
int sumQuantities(String path) {
final file = File(path).openSync();
print('opened $path');
try {
final text = utf8.decode(file.readSync(file.lengthSync()));
var total = 0;
for (final line in const LineSplitter().convert(text)) {
total += int.parse(line.split(',')[1]);
}
return total;
} finally {
file.closeSync();
print('closed $path');
}
}
void main() {
File('orders.csv').writeAsStringSync('pens,3\npads,12\nclips,5\n');
File('orders_bad.csv').writeAsStringSync('pens,3\npads,12a\nclips,5\n');
print('good file: ${sumQuantities('orders.csv')} items');
try {
sumQuantities('orders_bad.csv');
} on FormatException catch (e) {
print('bad file: ${e.message}, "${e.source}"');
}
} Output
opened orders.csv closed orders.csv good file: 20 items opened orders_bad.csv closed orders_bad.csv bad file: Invalid radix-10 number, "12a"
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run finally_close.dart
Look at the order of the last three lines: closed orders_bad.csv came before bad file:. The finally block ran
while the FormatException was on its way out of sumQuantities, then main caught it. Without finally, the
failing run would have left the file open.
There is one way out that skips finally: exit() from dart:io ends the process on the spot. This program calls it
inside a try:
// exit() ends the process on the spot: no finally block runs, and no later line either.
import 'dart:io';
void main() {
try {
print('writing the report');
exit(2);
} finally {
print('finally: report file closed');
}
} Output (exit status 2)
writing the report
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run exit_skips_finally.dart
The program stopped with exit code 2 and never printed its finally line. The dart:io documentation warns that
exit runs no finally blocks and does not wait for asynchronous work, so it is likely to lose data. To end a command
line program with a code of your choice, set exitCode from dart:io and let main return normally.
Checkpoint
Check: finally on every way out
rethrow keeps the stack trace that throw e replaces
Sometimes a function catches a failure only to log it or count it, and then passes it on. How you pass it on decides
what the stack trace shows. catch (e, stack) gives you the trace as a StackTrace. This program passes the same
FormatException on in three ways and prints the first frame of its own file in each trace:
// Three ways to pass a FormatException on, and the first frame of this file each stack trace shows.
int parseQty(String s) => int.parse(s);
int withRethrow(String s) {
try {
return parseQty(s);
} on FormatException {
rethrow;
}
}
int withThrowE(String s) {
try {
return parseQty(s);
} on FormatException catch (e) {
throw e;
}
}
int withWrap(String s) {
try {
return parseQty(s);
} on FormatException catch (_, stack) {
Error.throwWithStackTrace(ArgumentError.value(s, 'qty', 'not a whole number'), stack);
}
}
void main() {
final ways = [('rethrow', withRethrow), ('throw e', withThrowE), ('throwWithStackTrace', withWrap)];
for (final (label, parse) in ways) {
try {
parse('7x');
} catch (e, stack) {
final frame = stack.toString().split('\n').firstWhere((l) => l.contains('traces.dart'));
print('$label: ${e.runtimeType}');
print(' first frame here: ${frame.substring(frame.indexOf(' ')).trim()}');
}
}
} Output
rethrow: FormatException first frame here: parseQty (traces.dart:2:31) throw e: FormatException first frame here: withThrowE (traces.dart:16:5) throwWithStackTrace: ArgumentError first frame here: parseQty (traces.dart:2:31)
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run traces.dart
With rethrow: A statement inside a catch block that throws the caught object again with its original stack trace, so the trace still starts where the failure happened. the trace still starts in parseQty, at line 2, where int.parse failed.
throw e throws the same object again but starts a new trace at its own line, 16, so whoever reads the log learns
where it was thrown again, not where it went wrong. Effective Dart’s rule is short: use rethrow to pass a caught
exception on.
To pass on a different object, use Error.throwWithStackTrace(newObject, stack) (in dart:core since Dart 2.16). The
third run threw an ArgumentError that names the parameter, and its trace still starts at line 2. That is how to
change what a failure says without losing where it came from.
Read the first frames that are yours
The top frames of a trace are often inside the SDK (int._handleFormatError, int.parse). Scan down to the first
frame in your own file: that is the line to read first.
Checkpoint
Check: Stack traces, rethrow and wrapping
A custom exception names the field, the input and the reason
FormatException: Invalid radix-10 number tells a developer something, but a form needs to say which field failed
and what to do. Dart can throw any non-null object, even a string, but a class that implements Exception can be
caught by type and carry data. Here is one for a coupon field:
// A custom exception that says which field failed, what was typed and why.
class CouponException implements Exception {
CouponException(this.field, this.input, this.reason);
final String field;
final String input;
final String reason;
@override
String toString() => 'CouponException($field): "$input" $reason';
}
final _allowed = RegExp(r'^[A-Z0-9]+$');
String readCoupon(String input, {String field = 'coupon'}) {
final code = input.trim().toUpperCase();
if (code.length < 4 || code.length > 12) {
throw CouponException(field, input, 'has ${code.length} characters; use 4 to 12');
}
if (!_allowed.hasMatch(code)) {
throw CouponException(field, input, 'may hold only letters and digits');
}
return code;
}
void main() {
for (final typed in ['festive50', ' new10 ', 'diwali 50', 'ok', 'SAVE-20']) {
try {
print('accepted ${readCoupon(typed)}');
} on CouponException catch (e) {
print('refused: $e');
}
}
} Output
accepted FESTIVE50 accepted NEW10 refused: CouponException(coupon): "diwali 50" may hold only letters and digits refused: CouponException(coupon): "ok" has 2 characters; use 4 to 12 refused: CouponException(coupon): "SAVE-20" may hold only letters and digits
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run coupon.dart
Each refusal names the field, shows what was typed and gives a reason a person can act on: too short, or a character
that is not allowed. The class implements Exception, so on CouponException catches exactly these and
on Exception catches them along with the others. A thrown string can be caught only by a catch with no on,
which is why the only_throw_errors lint asks you to throw only classes that extend Exception or Error. A
custom exception: A class of your own that implements Exception and carries data about a failure, such as the field, the input and the reason, so callers can catch it by type and act on it. also gets a precise type: the Exception documentation discourages
library code from throwing a plain Exception("message") for exactly that reason.
Checkpoint
Check: Custom exceptions with context
Practise: a quantity parser with sample tests
The package below holds a QuantityException like the coupon one, a parseQuantity that is not written yet, and four
sample tests:
practice/pubspec.yaml
name: quantities
description: parseQuantity to write, with its exception type and four sample tests.
publish_to: none
environment:
sdk: ^3.13.0
dev_dependencies:
test: ^1.33.0 lib/quantities.dart · practice/lib/quantities.dart
/// Thrown when a typed quantity cannot be used: says which field, what was typed and why.
class QuantityException implements Exception {
QuantityException(this.field, this.input, this.reason);
final String field;
final String input;
final String reason;
@override
String toString() => 'QuantityException($field): "$input" $reason';
}
/// Reads a quantity of 1 to 999 typed into [field], ignoring spaces around it.
///
/// Throws a [QuantityException] for anything else.
int parseQuantity(String input, {String field = 'quantity'}) {
throw UnimplementedError('parseQuantity');
} test/quantities_test.dart · practice/test/quantities_test.dart
import 'package:quantities/quantities.dart';
import 'package:test/test.dart';
void main() {
test('a plain number is read', () {
expect(parseQuantity('3'), 3);
});
test('spaces around the number are ignored', () {
expect(parseQuantity(' 12 '), 12);
});
test('text that is not a whole number names its field', () {
expect(
() => parseQuantity('12a', field: 'pads'),
throwsA(isA<QuantityException>().having((e) => e.field, 'field', 'pads')),
);
});
test('zero is outside 1 to 999', () {
expect(
() => parseQuantity('0'),
throwsA(isA<QuantityException>().having((e) => e.reason, 'reason', contains('1 to 999'))),
);
});
} As handed out, parseQuantity throws UnimplementedError, so every sample test fails:
from session import files, run
# The quantities package as handed out: parseQuantity still throws, so all four sample tests fail.
files("practice=quantities")
run(
"""
cd quantities
dart pub get --offline > /dev/null
!dart test --reporter failures-only
""",
pub=True,
) Output
$ cd quantities
$ dart pub get --offline > /dev/null
$ dart test --reporter failures-only
+0 -1: test/quantities_test.dart: a plain number is read [E]
UnimplementedError: parseQuantity
package:quantities/quantities.dart 17:3 parseQuantity
test/quantities_test.dart 6:12 main.<fn>
+0 -2: test/quantities_test.dart: spaces around the number are ignored [E]
UnimplementedError: parseQuantity
package:quantities/quantities.dart 17:3 parseQuantity
test/quantities_test.dart 10:12 main.<fn>
+0 -3: test/quantities_test.dart: text that is not a whole number names its field [E]
Expected: throws <Instance of 'QuantityException'> with `field`: 'pads'
Actual: <Closure: () => int>
Which: threw UnimplementedError:<UnimplementedError: parseQuantity>
stack package:quantities/quantities.dart 17:3 parseQuantity
test/quantities_test.dart 15:13 main.<fn>.<fn>
package:matcher expect
test/quantities_test.dart 14:5 main.<fn>
which is not an instance of 'QuantityException'
package:matcher expect
test/quantities_test.dart 14:5 main.<fn>
+0 -4: test/quantities_test.dart: zero is outside 1 to 999 [E]
Expected: throws <Instance of 'QuantityException'> with `reason`: contains '1 to 999'
Actual: <Closure: () => int>
Which: threw UnimplementedError:<UnimplementedError: parseQuantity>
stack package:quantities/quantities.dart 17:3 parseQuantity
test/quantities_test.dart 22:13 main.<fn>.<fn>
package:matcher expect
test/quantities_test.dart 21:5 main.<fn>
which is not an instance of 'QuantityException'
package:matcher expect
test/quantities_test.dart 21:5 main.<fn>
+0 -4: Some tests failed.
Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
For example, 'dart test --chain-stack-traces'.
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 practice_session.py
- Copy the three files into a folder of your own, run
dart pub get, thendart test: four failures, as above. - Trim the input and read it with
int.parseinside atry. Turn itsFormatExceptioninto aQuantityExceptionthat names the field and says the text is not a whole number. - Refuse values below 1 and above 999 with a reason that contains
1 to 999. Rundart testuntil all four succeed. - Add tests of your own for
'','+5'and'99999999999999999999'.int.parsereads+5as 5 and throws aFormatExceptionfor the last one, so decide what your function should do with each and test it.
Then check what you have learned, one question at a time, the idea you know least first:
Practice that adapts to you
Practise this lesson
12 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): Exception or Error, which one belongs in a catch clause?
An Exception reports a failure the program can expect and do something about: a FormatException from text that
does not parse, an IOException from a file that is missing, a TimeoutException from a call that took too long.
Catch those with an on clause where you can recover, retry or tell the user. An Error such as StateError,
RangeError or TypeError reports a bug: the code asked for .first of an empty list or read an index past the end.
The Error documentation says callers should not expect or catch them, because the fix is in the code. Catching one
hides the bug; letting it reach the top gives a stack trace that points at the line to fix.
Warm-up (fresher): Why write rethrow instead of throw e inside a catch block?
Both send the same object on, but they leave different stack traces. rethrow keeps the original trace, so it still
starts at the line where the failure happened, inside the function that threw. throw e starts a new trace at the
throw itself, so the log points at the catch block instead of the cause. In traces.dart the first frame was
parseQty at line 2 with rethrow and withThrowE at line 16 with throw e. To throw a different object while
keeping the original trace, use Error.throwWithStackTrace.
Two longer questions start from these ideas and ask for a policy and a design:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A nightly job imports 40,000 supplier records, each inside try { ... } catch (e) { log(e); }. For three weeks an off-by-one RangeError skipped about 4 % of the records and nobody noticed. Propose a catch policy for the job and say how you would roll it out.
-
Mid to senior
A file-import library wraps every failure in its own OrderFileException(message). Since that change, production traces start inside the wrapper, and nobody can tell which parser or which line of the file failed. Fix the wrapper while keeping OrderFileException as the type callers catch.
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What happens when nothing catches a thrown exception in a Dart program?
The throw leaves the function that threw and every caller in turn. With no catch, the program prints "Unhandled exception:" and a stack trace and ends with exit code 255.
-
What does on FormatException catch (e, s) do, and in which order are clauses tried?
It handles one type of failure and names the thrown object and its stack trace. Clauses are tried from the top, so specific types go first; a catch with no on takes everything.
-
Why avoid a catch with no on clause around ordinary code?
It also catches bugs, such as the StateError of an empty cart, and reports them as bad input. Write on FormatException catch (e) for the failure you expect, so a bug still stops the program. Effective Dart: "avoid catches without on clauses".
-
Which belongs in a catch clause, an Exception or an Error?
An Exception, a failure the program can expect and handle, such as FormatException or TimeoutException. An Error such as StateError or RangeError reports a bug: its fix is a change to the code, so normal code does not catch it.
-
When does a finally block run, and when does it not?
On every way out of a try, an exception nobody caught included. exit() ends the process without running it.
-
Why write rethrow instead of throw e in a catch block?
rethrow keeps the original stack trace, starting where the failure happened; throw e starts a new trace at the catch block. Error.throwWithStackTrace throws a new object with the old trace.
-
What should a custom exception class carry?
It implements Exception and carries the field, the input and the reason, so callers can catch it by type and show a message a person can act on.
-
Where do you start reading a long stack trace?
At the first frame in your own file. The top frames are often inside the SDK, such as int._handleFormatError and int.parse.
Key takeaways
- A throw stops the function that threw and every caller in turn until a matching catch takes it; with no catch, a
Dart program prints
Unhandled exception:and a stack trace and ends with exit code 255. on Type catch (e, s)handles one type of failure and names the object and its stack trace; clauses are tried from the top, so specific types go first, and acatchwith noontakes everything, bugs included.- An
Exceptionis a condition to handle; anErrorsuch asStateErrororRangeErroris a bug to fix, so normal code does not catch it. finallyruns on every way out of atry, including an exception nobody caught, butexit()ends the process without running it.rethrowkeeps the original stack trace,throw estarts a new one, andError.throwWithStackTracethrows a new object with the old trace.- A custom exception implements
Exceptionand carries the field, the input and the reason, so callers can catch it by type and show a message a person can act on.
Terms in this lesson
- custom exception
- A class of your own that implements Exception and carries data about a failure, such as the field, the input and the reason, so callers can catch it by type and act on it.
- Error (programming error)
- An object of the Error class or a subtype, such as StateError, RangeError or TypeError, thrown when code does something its author should have avoided. It reports a bug to fix, not a condition to catch.
- exception
- An object a Dart program throws to report a failure it is expected to handle, such as text that does not parse or a file that is missing. Classes that implement Exception can be caught by type with an on clause.
- finally clause
- The block after try and its catch clauses that runs on every way out of the try: normal completion, a caught exception or one nobody caught. exit() from dart:io skips it.
- rethrow
- A statement inside a catch block that throws the caught object again with its original stack trace, so the trace still starts where the failure happened.
- stack trace
- The list of calls that were running when a value was thrown, newest first, each with its file, line and column. Dart prints it for an unhandled exception and hands it to catch (e, s) as a StackTrace.
- unhandled exception
- An exception that no catch clause took. A Dart command line program then prints Unhandled exception, the message and the stack trace on standard error, and exits with code 255.
References
- Error handling (The Dart project (Google))
- Exception class (The Dart project (Google))
- Error class (The Dart project (Google))
- Error.throwWithStackTrace (The Dart project (Google))
- FormatException class (The Dart project (Google))
- exit function (dart:io) (The Dart project (Google))
- exitCode property (dart:io) (The Dart project (Google))
- Effective Dart: Usage (Error handling) (The Dart project (Google))
- only_throw_errors lint rule (The Dart project (Google))
Report a problem with this lesson
Kept only in this browser. Your Learn progress