Question

How to Catch Multiple Exceptions at Once in C#

csharp.netexception

Question

In C#, it is generally discouraged to catch System.Exception because code should usually catch only the exceptions it actually expects and knows how to handle.

This can sometimes lead to repetitive code. For example:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

Is there a way to catch both exceptions while running the recovery code only once?

The example above is simple because it only deals with a Guid, but imagine a larger block of code where an object is modified several times, and if one of the expected operations fails, you want to reset that object. At the same time, if an unexpected exception occurs, it should still be allowed to propagate upward rather than being silently handled.

Short Answer

By the end of this page, you will understand how to handle multiple expected exceptions in C# without duplicating code. You will learn when to use multiple catch blocks, exception filters, and safer alternatives such as TryParse-style methods that avoid exceptions entirely for expected invalid input.

Concept

In C#, each catch block handles one exception type at a time. If multiple exception types should trigger the same recovery logic, beginners often duplicate the same code in several catch blocks.

That works, but it can become noisy and harder to maintain.

The key idea is this:

  • Catch only the exceptions you expect and can handle
  • Let unexpected exceptions continue upward
  • Prefer APIs that avoid exceptions for normal invalid input

In the Guid example, invalid input is not really an exceptional system failure. It is often just bad user input. In cases like that, a parsing method such as Guid.TryParse is usually better than using exceptions for control flow.

When you truly do need exception handling, C# gives you a few clean options:

  • Separate catch blocks with shared logic moved elsewhere
  • A single catch with an exception filter using when
  • Refactoring the risky code into smaller operations

Why this matters in real programming:

  • It reduces duplicated recovery code
  • It makes error handling easier to read
  • It avoids hiding real bugs behind broad exception handling
  • It encourages use of safer APIs for expected failure cases

Mental Model

Think of exception handling like airport security.

  • A known issue is something the staff is trained for, like a passenger carrying too much liquid.
  • An unknown issue is something unusual that must be escalated.

You do not want security to treat every problem the same way. Instead:

  • known problems get a standard response
  • unknown problems get passed to someone higher up

In C#, that means you should catch only the exception types you expect. If two known problems should lead to the same action, you can route both to the same response instead of writing the same recovery code twice.

Take Quiz

Syntax and Examples

Basic approach: separate catch blocks

A straightforward way is to keep separate catch blocks:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

This is valid C#, but the repeated assignment is not ideal.

Better approach for this case: Guid.TryParse

For parsing input, use TryParse when available:

if (!Guid.TryParse(queryString["web"], out var webId))
{
    WebId = Guid.Empty;
}
else
{
    WebId = webId;
}

This is cleaner because:

  • no exceptions are thrown for invalid input
  • the intent is obvious
  • it is usually more efficient than throwing and catching exceptions

You can shorten it further:

WebId = Guid.TryParse(queryString["web"], out var webId)
    ? webId
    : Guid.Empty;

Catch multiple exceptions with an exception filter

If you really need exception handling, C# supports exception filters:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

This catches only those two exception types and lets all others continue upward.

Reusable reset logic

If several exception types need the same cleanup, move the cleanup into a method:

try
{
    UpdateObject();
}
catch (FormatException)
{
    ResetObject();
}
catch (InvalidOperationException)
{
    ResetObject();
}

Or, with a filter:

try
{
    UpdateObject();
}
catch (Exception ex) when (ex is FormatException || ex is InvalidOperationException)
{
    ResetObject();
}

Step by Step Execution

Consider this example:

string input = "not-a-guid";
Guid webId;

if (Guid.TryParse(input, out var parsedId))
{
    webId = parsedId;
}
else
{
    webId = Guid.Empty;
}

Step-by-step

  1. input is assigned the value "not-a-guid".
  2. Guid.TryParse(input, out var parsedId) tries to convert that string to a Guid.
  3. The conversion fails because the text is not a valid GUID format.
  4. TryParse returns false instead of throwing an exception.
  5. The else block runs.
  6. webId is set to Guid.Empty.

Now compare that with exception-based code:

string input = "not-a-guid";
Guid webId;

try
{
    webId = new Guid(input);
}
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
    webId = Guid.Empty;
}

What happens here

  1. new Guid(input) runs.
  2. The constructor sees invalid text.
  3. A matching exception is thrown.
  4. The catch filter checks whether the exception is FormatException or OverflowException.
  5. Because it matches, the catch block runs.
  6. webId is set to Guid.Empty.
  7. If the exception were some other type, this catch block would be skipped and the exception would continue upward.

Real World Use Cases

1. Parsing user input

A web app receives values from query strings, forms, or JSON payloads.

if (!int.TryParse(form["age"], out var age))
{
    age = 0;
}

2. Resetting state after expected validation failures

A service updates an object in several steps. If a known validation-related exception occurs, it resets the object to a safe state.

try
{
    ApplyUserChanges(profile);
}
catch (Exception ex) when (ex is FormatException || ex is InvalidOperationException)
{
    profile.Reset();
}

3. File and configuration handling

Applications often treat some failures as expected, such as invalid config values.

try
{
    settings.Port = int.Parse(config["Port"]);
}
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
    settings.Port = 8080;
}

4. API input validation

Backend code commonly validates IDs, dates, and numeric values from requests. For normal invalid input, developers usually prefer validation or TryParse instead of exceptions.

Take Quiz

Real Codebase Usage

In real C# codebases, developers usually follow these patterns:

Prefer TryParse for expected invalid input

If bad input is normal, avoid exceptions:

if (!DateTime.TryParse(request.DateText, out var date))
{
    return BadRequest("Invalid date.");
}

Use guard clauses early

Reject bad values before deeper logic runs:

if (string.IsNullOrWhiteSpace(queryString["web"]))
{
    WebId = Guid.Empty;
    return;
}

Use exception filters for shared handling

When multiple exception types need the same response, filters reduce duplication:

catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
    ResetObject();
}

Keep try blocks small

A small try block makes it clearer which code might fail and which exceptions are expected:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

Avoid swallowing unexpected exceptions

Catching broad exceptions without filtering can hide bugs:

catch (Exception)
{
    WebId = Guid.Empty; // risky if this hides unrelated failures
}

Common Mistakes

Catching Exception without a good reason

Broken example:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception)
{
    WebId = Guid.Empty;
}

Why it is a problem:

  • it may hide unrelated bugs
  • it makes debugging harder
  • it handles errors you did not intend to handle

Using exceptions for normal control flow

Broken example:

try
{
    age = int.Parse(input);
}
catch (FormatException)
{
    age = 0;
}

Better:

age = int.TryParse(input, out var parsedAge) ? parsedAge : 0;

Putting too much code inside one try

Broken example:

try
{
    LogRequest();
    WebId = new Guid(queryString["web"]);
    SaveToDatabase();
}
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

Why it is a problem:

  • the filter may accidentally catch exceptions from code you did not mean to group together
  • it becomes harder to reason about what failed

Better:

LogRequest();

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

SaveToDatabase();

Assuming all parsing APIs behave the same way

Some APIs have TryParse; some do not. Check the documentation for the specific type before choosing your pattern.

Resetting state incompletely

If you modify an object in several steps, make sure your recovery logic fully restores a valid state. Partial resets can leave objects inconsistent.

Comparisons

ApproachBest forProsCons
Multiple catch blocksA few exception types with distinct actionsClear and explicitCan duplicate code
Exception filter with whenMultiple exception types with the same handlingReduces duplication, keeps unexpected exceptions unhandledSlightly more advanced syntax
TryParseExpected invalid inputNo exceptions, clearer intent, efficientOnly works when the API provides it
Catch ExceptionRare top-level boundaries like app startup or loggingCan prevent app crash at system boundariesToo broad for normal business logic

new Guid(...) vs Guid.TryParse(...)

OptionBehavior on invalid inputRecommended for user input?
new Guid(text)Throws an exceptionUsually no
Guid.TryParse(text, out value)Returns falseYes

Separate catches vs filter

catch (FormatException)
{
    ResetObject();
}
catch (OverflowException)
{
    ResetObject();
}

vs

catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
    ResetObject();
}

Use separate catches when you want maximum explicitness. Use a filter when the recovery action is identical.

Take Quiz

Cheat Sheet

Quick rules

  • Catch only exceptions you expect and can handle.
  • Do not catch Exception just to avoid writing more specific code.
  • For parsing user input, prefer TryParse over exceptions.
  • Keep try blocks small.
  • Use exception filters with when if multiple exception types share the same handling.

Common patterns

Preferred for parsing

WebId = Guid.TryParse(queryString["web"], out var webId)
    ? webId
    : Guid.Empty;

Exception filter

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

Separate catch blocks

try
{
    DoWork();
}
catch (FormatException)
{
    ResetObject();
}
catch (InvalidOperationException)
{
    ResetObject();
}

Edge cases

  • A broad catch (Exception ex) when (...) is still safe if the filter only matches the exceptions you want.
  • If the API offers TryParse, that is usually better than try/catch for invalid input.
  • If cleanup must always run, use finally for unconditional cleanup, not for selective error handling.

FAQ

Can C# catch multiple exceptions in one catch block?

Yes. A common modern approach is an exception filter:

catch (Exception ex) when (ex is FormatException || ex is OverflowException)

Is it bad to catch System.Exception in C#?

Usually yes for normal application logic, because it is too broad. It is sometimes acceptable at top-level boundaries such as application startup, logging, or global error handling.

What is better than catching parsing exceptions in C#?

Use TryParse methods when available, such as int.TryParse, DateTime.TryParse, or Guid.TryParse.

Should I use new Guid(string) or Guid.TryParse?

For user input or external input, prefer Guid.TryParse. It is clearer and avoids exception-based control flow.

Will exception filters hide unexpected exceptions?

No. If the filter condition does not match, the exception is not handled by that catch block and continues upward.

When should I keep separate catch blocks instead of using a filter?

Use separate catch blocks when each exception type needs a different response or when you want the code to be more explicit for readability.

Take Quiz

Mini Project

Description

Build a small C# input parser that reads a user-provided string and safely converts it into a Guid. The project demonstrates the difference between exception-based parsing and TryParse, and shows how to provide a fallback value when input is invalid.

Goal

Create a program that attempts to read a GUID string and stores either the parsed value or Guid.Empty if the input is invalid.

Requirements

  • Read a string value that represents a possible GUID.
  • Attempt to convert it to a Guid safely.
  • If the conversion fails, store Guid.Empty.
  • Print the final Guid value.
  • Use a clean approach suitable for expected invalid input.
Take Quiz

Keep learning

Related questions

View more