Multiple Catch Blocks in C#

Vaibhav • September 10, 2025

In the previous article, we explored how exceptions propagate through the call stack and how they can be caught or rethrown. Now we turn our attention to a powerful feature of C# exception handling: multiple catch blocks. This technique allows you to handle different types of exceptions in different ways, making your code more robust, readable, and maintainable.

Exception handling is not just about catching errors - it’s about responding appropriately to different failure modes. A file not found, a null reference, or a divide-by-zero error may all require different actions. Multiple catch blocks give you the flexibility to do just that.

Why Use Multiple Catch Blocks?

When an exception is thrown, the runtime searches for a matching catch block. If you only use a single generic catch, you lose the ability to distinguish between different error types. Multiple catch blocks allow you to:

  • Handle specific exceptions with tailored logic
  • Log different errors differently
  • Recover from some errors while failing fast on others
  • Avoid masking bugs by catching too broadly

The order of catch blocks matters. The runtime picks the first block that matches the exception type. Always place more specific exceptions before general ones.

Basic Syntax of Multiple Catch Blocks

Here’s the basic structure:

try
{
    // Code that may throw exceptions
}
catch (FileNotFoundException ex)
{
    Console.WriteLine("File missing: " + ex.Message);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Math error: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("General error: " + ex.Message);
}

In this example:

  • If a file is missing, the first block handles it.
  • If there’s a divide-by-zero, the second block runs.
  • Any other exception falls through to the generic block.

How Matching Works

The runtime matches exceptions by type. If the thrown exception is a subclass of the type in the catch block, it matches. For example:

catch (IOException ex) { ... }

This will catch FileNotFoundException, DirectoryNotFoundException, and other exceptions that inherit from IOException.

If you place catch (Exception) before catch (IOException), the latter will never run. The broader catch “shadows” the more specific one.

Real-World Example: File Processing

Let’s say you’re building a tool that reads data from a file and performs calculations. You want to handle file errors, math errors, and unexpected issues separately:

try
{
    string content = File.ReadAllText("data.txt");
    int number = int.Parse(content);
    int result = 100 / number;
    Console.WriteLine("Result: " + result);
}
catch (FileNotFoundException ex)
{
    Console.WriteLine("Error: File not found.");
}
catch (FormatException ex)
{
    Console.WriteLine("Error: File content is not a valid number.");
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Error: Cannot divide by zero.");
}
catch (Exception ex)
{
    Console.WriteLine("Unexpected error: " + ex.Message);
}

This example shows how multiple catch blocks let you respond precisely to each kind of failure. You can also log each error differently or take different recovery actions.

Using When Clauses for Filtering

C# supports when clauses to add conditions to catch blocks. This lets you filter exceptions based on runtime data:

catch (IOException ex) when (ex.Message.Contains("disk full"))
{
    Console.WriteLine("Disk is full. Please free up space.");
}

The when clause acts like an if statement for exceptions. If the condition is false, the catch block is skipped.

You can use when clauses to avoid catching exceptions you don’t want to handle - for example, only catching IOException if it’s not a timeout.

Best Practices for Multiple Catch Blocks

Multiple catch blocks are powerful, but they can be misused. Here are some guidelines:

  • Be specific: Catch the most specific exception you can. Don’t use Exception unless you truly need a fallback.
  • Order matters: Put specific catches before general ones.
  • Don’t swallow exceptions: Always log or rethrow if you’re not handling the error fully.
  • Use when clauses: Filter exceptions when needed.
  • Avoid empty catch blocks: They hide bugs and make debugging harder.

Always include a final catch (Exception) block to catch unexpected errors - but make sure it logs the issue or rethrows. Never let exceptions disappear silently.

Multiple Catch Blocks with Custom Exceptions

In the previous article, we created custom exceptions like ValidationException and DataAccessException. You can catch these just like built-in types:

try
{
    ValidateUserInput();
    SaveToDatabase();
}
catch (ValidationException ex)
{
    Console.WriteLine("Validation failed: " + ex.Message);
}
catch (DataAccessException ex)
{
    Console.WriteLine("Database error: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("Unexpected error: " + ex.Message);
}

This lets you separate business logic errors from infrastructure errors, and respond accordingly.

Multiple Catch Blocks in Asynchronous Code

In async methods, exceptions are caught the same way - but you must await the task to trigger the exception:

try
{
    await LoadDataAsync();
}
catch (TimeoutException ex)
{
    Console.WriteLine("Operation timed out.");
}
catch (HttpRequestException ex)
{
    Console.WriteLine("Network error: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("Unexpected async error: " + ex.Message);
}

This pattern is essential for handling network errors, timeouts, and other async-specific issues.

Common Mistakes to Avoid

Multiple catch blocks are easy to misuse. Here are some pitfalls:

  • Incorrect order: Catching Exception before specific types makes the latter unreachable.
  • Empty blocks: Catching without logging or handling hides problems.
  • Overly broad catches: Catching Exception everywhere makes debugging harder.
  • Missing fallback: Not catching unexpected exceptions can crash your app.

Use multiple catch blocks to clarify intent - not to silence errors. Every catch block should either handle, log, or rethrow the exception.

Summary

Multiple catch blocks are a key feature of C# exception handling. They let you respond to different error types with precision and clarity. By matching exceptions by type, ordering blocks correctly, and using when clauses for filtering, you can write error-handling code that’s both robust and readable.

You’ve learned how to structure multiple catch blocks, how matching works, how to use custom exceptions, and how to apply these patterns in both synchronous and asynchronous code. In the next article, we’ll explore Exception Properties - the rich data that exceptions carry, and how to use it to diagnose and respond to errors effectively.