In Dart, we can create custom exceptions by defining our own exception class that either implements or extends the built-in Exception class. This approach helps us represent meaningful, domain-specific errors instead of relying on generic exceptions like FormatException or StateError.
For example, in one of my Flutter projects, I created a custom exception to handle invalid API responses more clearly. Instead of just throwing a general error, I defined a custom exception like this:
class InvalidApiResponseException implements Exception {
final String message;
InvalidApiResponseException(this.message);
@override
String toString() => 'InvalidApiResponseException: $message';
}
Then, I used it in a function that fetched data from an API:
Future fetchUserData() async {
final response = await fetchFromServer(); // simulate an API call
if (response.statusCode != 200) {
throw InvalidApiResponseException('Server returned ${response.statusCode}');
} else {
print('Data fetched successfully');
}
}
When calling this function, I can handle the error gracefully:
void main() async {
try {
await fetchUserData();
} on InvalidApiResponseException catch (e) {
print(e); // Specific handling for API errors
} catch (e) {
print('Unexpected error: $e');
}
}
This pattern makes the error-handling logic more expressive and helps identify the exact cause of failure, which is very useful when debugging or showing user-friendly error messages.
I’ve used this approach in a project where I had multiple types of exceptions like NetworkException, ValidationException, and CacheException. It helped categorize errors based on their origin — for example, I could show a “No Internet Connection” message for NetworkException and a “Invalid Credentials” message for ValidationException.
One challenge I faced was overusing too many custom exception types, which made error handling repetitive. To overcome that, I grouped similar errors into a base class like AppException, then derived specific exceptions from it. That way, I could handle both general and specific cases easily.
A limitation of Dart’s exception system is that it’s not as structured as some other languages — Dart doesn’t enforce checked exceptions, so developers must be disciplined about catching and throwing meaningful errors. However, this flexibility also keeps the code cleaner.
As an alternative, when I need more predictable error management (especially in functional-style code), I sometimes use Result or Either types from the dartz package instead of throwing exceptions. They allow handling errors as data, making the flow more controlled and testable.
So overall, creating custom exceptions in Dart is straightforward — you define a class implementing Exception, throw it where necessary, and handle it using try-catch. It makes your code more maintainable, readable, and expressive when dealing with specific application errors.
