Dart Fundamentals: The Language Behind Flutter · Classes, Modern Dart 3 Features and Async
Errors and Exceptions in Dart
Throw and catch exceptions, use on/catch/finally, define custom exception types, rethrow with context, and know the difference between Exceptions (expected failures) and Errors (bugs).
What you'll build
A payment validator that throws specific exceptions for different problems, a caller that handles each type differently, and a finally block that always cleans up.
The idea
throw SomeException('message')raises;try { } on Type catch (e) { } catch (e, stackTrace) { } finally { }handles.on Typecatches a specific type;catch (e)catches anything;finallyalways runs (close files, hide spinners).rethrowre-raises inside acatchwhile keeping the original stack trace.Exceptionis for expected, recoverable problems (bad input, network failures); implement it for your own types.Error(likeStateError,ArgumentError,RangeError) signals programming bugs; generally don't catch those, fix them.- In async code,
try/catchworks aroundawait; unawaited futures report errors elsewhere.
Some teams prefer returning a result type (a sealed Result with Ok and Err, as in the previous lesson's patterns) for expected failures, keeping exceptions for truly exceptional cases.
The code
class PaymentException implements Exception {
final String message;
const PaymentException(this.message);
@override
String toString() => 'PaymentException: $message';
}
class CardDeclined extends PaymentException {
const CardDeclined() : super('card declined');
}
class InsufficientFunds extends PaymentException {
final int shortByCents;
const InsufficientFunds(this.shortByCents) : super('insufficient funds');
}
void charge(int cents, {required int balance, bool blocked = false}) {
if (cents <= 0) throw ArgumentError.value(cents, 'cents', 'must be positive'); // a bug in the caller
if (blocked) throw const CardDeclined();
if (cents > balance) throw InsufficientFunds(cents - balance);
}
String checkout(int cents, {required int balance, bool blocked = false}) {
var spinner = 'on';
try {
charge(cents, balance: balance, blocked: blocked);
return 'paid ${(cents / 100).toStringAsFixed(2)}';
} on InsufficientFunds catch (e) {
return 'short by ${(e.shortByCents / 100).toStringAsFixed(2)}: try a smaller amount';
} on PaymentException catch (e) {
return 'payment failed: ${e.message}';
} finally {
spinner = 'off'; // always runs, even after return
print(' (spinner $spinner)');
}
}
void main() {
print(checkout(2500, balance: 10000));
print(checkout(12000, balance: 10000));
print(checkout(500, balance: 10000, blocked: true));
try {
try {
checkout(-1, balance: 100);
} catch (e) {
print('logging: ${e.runtimeType}');
rethrow; // keep the original error and stack trace
}
} on ArgumentError catch (e) {
print('bug found: ${e.message} (${e.invalidValue})');
}
}
Output
(spinner off) paid 25.00 (spinner off) short by 20.00: try a smaller amount (spinner off) payment failed: card declined (spinner off) logging: ArgumentError bug found: must be positive (-1)
Run this on your computer — see “Get Started” for setup.
Common mistakes
- Catching everything with a bare
catchand ignoring it, which hides bugs. - Catching
Errors likeRangeErrororStateError; these mean the code is wrong. - Throwing strings (
throw 'oops'); throw typed exceptions so callers can handle them specifically. throw einstead ofrethrow, losing the original stack trace.
Key points
try/on Type catch/catch/finally; order handlers from specific to general.- Custom exceptions implement
Exception;Errorsubclasses indicate bugs. rethrowpreserves the stack trace.- Consider sealed result types for expected failures.
Further reading: Error handling.
Practice questions
What's the difference between Exception and Error in Dart?
Answer
Exceptions represent expected, recoverable conditions (invalid input, network failure) that callers may handle. Errors (StateError, ArgumentError, RangeError…) indicate programming bugs that should be fixed rather than caught.
Why use rethrow instead of throw e inside a catch block?
Answer
rethrow re-raises the same exception while preserving its original stack trace; throw e starts a new stack trace from the catch block, losing where the error actually happened.
Open in the app to mark lessons complete, take notes and track your progress.