Introduction
The try-catch construct protects code that might throw. Code in the try block runs normally; if an exception is thrown, execution immediately jumps to a matching catch or on clause.
try {
// Risky code
var result = int.parse('not-a-number');
} on FormatException catch (e) {
print('Could not parse: $${e.message}');
}
Multiple Catch Blocks
void processData(String input, int index) {
try {
var number = int.parse(input);
var list = [10, 20, 30];
print('Result: $${list[index] ~/ number}');
} on FormatException catch (e) {
print('Bad format: $${e.message}');
} on RangeError catch (e) {
print('Index out of range: $${e.message}');
} on IntegerDivisionByZeroException {
print('Cannot divide by zero!');
} catch (e) {
print('Unexpected error: $e');
}
}
void main() {
processData('2', 1); // Result: 10
processData('abc', 0); // Bad format: ...
processData('5', 10); // Index out of range: ...
processData('0', 0); // Cannot divide by zero!
}
Stack Trace
The second parameter in catch (e, stackTrace) captures the call stack at the point of the error — useful for debugging:
void riskyOperation() {
throw StateError('Something went wrong');
}
void main() {
try {
riskyOperation();
} catch (e, stackTrace) {
print('Error: $e');
print('Stack:\n$stackTrace');
}
}
Rethrow
Use rethrow to re-throw the current exception (preserving the original stack trace), useful for logging before propagating:
void process() {
try {
throw FormatException('Invalid data');
} catch (e) {
print('Logging error: $e');
rethrow; // re-throws with original stack trace
}
}
void main() {
try {
process();
} catch (e) {
print('Handled at top level: $e');
}
}
Output:Logging error: FormatException: Invalid data
Handled at top level: FormatException: Invalid data
Comprehensive Example
class NetworkException implements Exception {
final int statusCode;
final String message;
NetworkException(this.statusCode, this.message);
@override String toString() => 'NetworkException($statusCode): $message';
}
Future<String> fetchData(String url) async {
if (url.isEmpty) throw ArgumentError('URL cannot be empty');
if (!url.startsWith('https')) throw NetworkException(403, 'HTTPS required');
return 'Data from $url';
}
void main() async {
final urls = ['', 'http://example.com', 'https://example.com'];
for (var url in urls) {
try {
var data = await fetchData(url);
print('Success: $data');
} on ArgumentError catch (e) {
print('Argument error: $${e.message}');
} on NetworkException catch (e) {
print('Network error: $e');
} catch (e, st) {
print('Unknown: $e\n$st');
}
}
}
🧠 Quiz
1. What is the difference between catch (e) and on ExceptionType catch (e)?
- A) No difference
- B) on catches a specific type; catch catches all ✅
- C) catch is for async code only
- D) on is deprecated
2. What does rethrow preserve that throw e does not?
- A) The error message
- B) The original stack trace ✅
- C) The exception type
- D) The catch block
Summary
Use on ExceptionType catch (e) to catch specific exceptions and bare catch (e, stackTrace) for a fallback. Use rethrow to propagate an exception while preserving the stack trace. Order on clauses from most specific to least specific.