Exception Handling
Throwing, catching, and designing your own exceptions with try, catch, on, and finally.
읽는 데 3분
Not every failure fits neatly into a return value — sometimes the clearest way to signal "this operation didn't work" is to stop normal execution entirely. Dart's exception model gives you that, built around try, catch, and throw.
Throwing and catching
void withdraw(double balance, double amount) {
if (amount > balance) {
throw ArgumentError('Insufficient funds');
}
print('Withdrew \$$amount');
}
void main() {
try {
withdraw(100, 150);
} catch (e) {
print('Error: $e');
}
print('Program continues normally');
}Without the try/catch, an uncaught exception would crash the program entirely at the throw line. With it, control jumps straight to catch, and everything after — print('Program continues normally') — still runs.
Catching specific exception types
Dart lets you catch by type with on, which matters once your code can fail for more than one reason and needs to react differently to each:
void parseAge(String input) {
try {
final age = int.parse(input);
if (age < 0) throw RangeError('Age cannot be negative');
print('Age: $age');
} on FormatException {
print('That is not a valid number');
} on RangeError catch (e) {
print('Invalid range: ${e.message}');
} catch (e) {
print('Unexpected error: $e');
}
}
void main() {
parseAge('abc'); // That is not a valid number
parseAge('-5'); // Invalid range: Age cannot be negative
parseAge('30'); // Age: 30
}Dart checks each on clause in order and runs the first one that matches the thrown type; the plain catch (e) at the end acts as a catch-all for anything not already handled. Catching specific types instead of always using a bare catch (e) matters because it stops you from accidentally swallowing an error you didn't anticipate and don't actually know how to recover from.
finally: cleanup that always runs
void processFile() {
print('Opening file');
try {
throw Exception('Read error');
} catch (e) {
print('Handled: $e');
} finally {
print('Closing file');
}
}The finally block runs whether or not an exception was thrown, and whether or not it was caught — making it the right place for cleanup (closing a file, releasing a lock) that must happen regardless of how the try block ends.
Custom exceptions
For anything beyond a quick script, defining your own exception types documents what specifically went wrong, and lets calling code catch precisely that failure:
class InsufficientFundsException implements Exception {
final double requested;
final double available;
InsufficientFundsException(this.requested, this.available);
@override
String toString() =>
'InsufficientFundsException: requested \$$requested, only \$$available available';
}
void withdraw(double balance, double amount) {
if (amount > balance) {
throw InsufficientFundsException(amount, balance);
}
}
void main() {
try {
withdraw(50, 100);
} on InsufficientFundsException catch (e) {
print(e); // uses toString() automatically
}
}implements Exception is a convention, not a strict requirement — Dart actually allows you to throw any object at all, even a plain String. Implementing Exception (an effectively empty marker interface) simply signals to other developers, and to tools, that this type is meant to represent a recoverable failure rather than a programming bug — which is exactly what Error and its subtypes (like the RangeError above) are for instead.