throw
Understand how to throw exceptions in Dart.
What is it?
The throw keyword is used to explicitly raise (throw) an exception in Dart. When an exception is thrown, the normal flow of execution is interrupted, and the runtime looks for the nearest catch block to handle it.
Why does it exist?
throw exists to:
- Signal exceptional conditions
- Indicate invalid operations
- Validate inputs and states
- Control program flow
- Provide error context
- Enable structured error handling
Basic throw
Throwing Exceptions
The
throwkeyword raises an exception that can be caught and handled.
void validateAge(int age) {
if (age < 0) {
throw Exception('Age cannot be negative');
}
if (age < 18) {
throw Exception('Must be at least 18 years old');
}
print('Age is valid: $age');
}
void main() {
try {
validateAge(-5);
} catch (e) {
print('Error: $e');
}
try {
validateAge(15);
} catch (e) {
print('Error: $e');
}
// This works fine
validateAge(25);
}
// Output:
// Error: Exception: Age cannot be negative
// Error: Exception: Must be at least 18 years old
// Age is valid: 25
What's happening here? -
throwcreates and raises an exception - Program flow jumps to the nearestcatch- Different conditions throw different messages - Valid inputs don't throw exceptions
Throwing Different Types
Built-in Exception Types
Dart provides various built-in exception types for common scenarios.
void main() {
// Throw FormatException
try {
throw FormatException('Invalid number format');
} catch (e) {
print('FormatException: $e');
}
// Throw ArgumentError
try {
throw ArgumentError('Invalid argument provided');
} catch (e) {
print('ArgumentError: $e');
}
// Throw RangeError
try {
throw RangeError('Value out of range');
} catch (e) {
print('RangeError: $e');
}
// Throw StateError
try {
throw StateError('Invalid state');
} catch (e) {
print('StateError: $e');
}
// Throw TypeError
try {
throw TypeError();
} catch (e) {
print('TypeError: $e');
}
}
Throwing with Stack Trace
Capturing Stack Traces
You can provide a stack trace when throwing exceptions.
void functionA() {
functionB();
}
void functionB() {
functionC();
}
void functionC() {
// Throw with stack trace
throw Exception('Error in functionC');
}
void main() {
try {
functionA();
} catch (e, stackTrace) {
print('Error: $e');
print('Stack trace:');
print(stackTrace);
}
}
Conditional Throws
Throwing Based on Conditions
You can use conditionals to decide when to throw exceptions.
class BankAccount {
double _balance = 0;
void deposit(double amount) {
if (amount <= 0) {
throw ArgumentError('Deposit amount must be positive');
}
_balance += amount;
print('Deposited: \$${amount.toStringAsFixed(2)}');
}
void withdraw(double amount) {
if (amount <= 0) {
throw ArgumentError('Withdrawal amount must be positive');
}
if (amount > _balance) {
throw Exception('Insufficient funds');
}
_balance -= amount;
print('Withdrew: \$${amount.toStringAsFixed(2)}');
}
void transfer(BankAccount to, double amount) {
if (to == this) {
throw Exception('Cannot transfer to self');
}
withdraw(amount);
to.deposit(amount);
print('Transferred: \$${amount.toStringAsFixed(2)}');
}
double get balance => _balance;
}
void main() {
var account = BankAccount();
try {
account.deposit(-50);
} catch (e) {
print('Error: $e');
}
try {
account.withdraw(100);
} catch (e) {
print('Error: $e');
}
// Valid operations
account.deposit(100);
account.withdraw(50);
print('Balance: \$${account.balance.toStringAsFixed(2)}');
}
Throwing in Async Functions
Async throw
You can throw exceptions in async functions just like sync functions.
import 'dart:async';
Future<String> fetchData(String url) async {
if (url.isEmpty) {
throw ArgumentError('URL cannot be empty');
}
if (!url.startsWith('http')) {
throw FormatException('Invalid URL format');
}
await Future.delayed(Duration(seconds: 1));
// Simulate network error
if (url == 'error') {
throw Exception('Network error');
}
return 'Data from $url';
}
void main() async {
try {
var data = await fetchData('');
} catch (e) {
print('Error 1: $e');
}
try {
var data = await fetchData('error');
} catch (e) {
print('Error 2: $e');
}
try {
var data = await fetchData('https://example.com');
print('Data: $data');
} catch (e) {
print('Error 3: $e');
}
}
// Output:
// Error 1: ArgumentError: URL cannot be empty
// Error 2: Exception: Network error
// Data: Data from https://example.com
Key insights: -
throwworks in async functions - The exception is caught bytry-catch- The function returns aFuturethat completes with an error
Throwing in Streams
Error in Streams
Streams can throw errors using
throwinasync*generators.
import 'dart:async';
Stream<int> numberStream(int max) async* {
for (var i = 1; i <= max; i++) {
if (i == 3) {
throw Exception('Error at value 3');
}
await Future.delayed(Duration(milliseconds: 500));
yield i;
}
}
void main() async {
try {
await for (var value in numberStream(5)) {
print('Value: $value');
}
} catch (e) {
print('Stream error: $e');
}
// With listen
numberStream(5).listen(
(value) => print('Value: $value'),
onError: (error) => print('Error: $error'),
);
}
// Output:
// Value: 1
// Value: 2
// Stream error: Exception: Error at value 3
// Value: 1
// Value: 2
// Error: Exception: Error at value 3
// Value: 4
// Value: 5
Best Practices
Throw Descriptive Exceptions
// Good: Descriptive exception
throw ArgumentError('Username cannot be empty');
// Better: Custom exception with context
throw ValidationException(
field: 'username',
message: 'Username cannot be empty',
value: '',
);
// Bad: Generic exception
throw Exception('Error');
Use Specific Exception Types
// Good: Specific type
throw ArgumentError('Invalid argument');
// Good: Custom type
throw NetworkException(statusCode: 404, message: 'Not found');
// Bad: Generic
throw Exception('Error occurred');
Common Mistakes
Throwing Non-Exception Objects
Wrong:
throw 'Error message'; // Throwing a string
Correct:
throw Exception('Error message'); // Throwing an Exception
Throwing Inside Finally
Wrong:
try {
riskyOp();
} finally {
throw Exception('Error in finally'); // May mask original error
}
Correct:
try {
riskyOp();
} catch (e) {
// Handle original error
print('Original error: $e');
} finally {
// Cleanup only, don't throw
cleanup();
}
Summary
throw is used to raise exceptions, signaling that something unexpected happened. Use specific exception types and provide descriptive messages.
Next Steps
Now that you understand throw, continue to:
Did You Know?
throwworks in both sync and async code- You can throw any object that implements
Exception throwcan be used withoutcatchrethrowre-throws a caught exceptionthrowinsidefinallymasks original errors- Use
onto catch specific exception types - Stack traces show where the error occurred