throw vs throw ex in C#: Why throw ex Destroys Your Stack Trace

Both throw; and throw ex; rethrow an exception from a catch block, but they leave very different evidence behind. throw; keeps the original stack trace. throw ex; resets it to the line in the catch block, so the log points at your error handler instead of the code that actually failed. This guide shows the difference, the right way to add context, and how to find every throw ex in a codebase.

Video explainer (1:22) · Watch with transcript

The difference in one sentence

throw; rethrows the exception currently being handled and keeps its stack trace intact. throw ex; throws the same exception object again as if it started in the catch block, so every frame below that point is lost from the trace.

The exception type and message are identical either way. Only the stack trace changes, which is exactly the part you need when you debug a production failure.

Before: throw ex hides the failing line

public void ProcessOrder(int orderId)
{
    try
    {
        var order = _repository.Load(orderId);
        _payments.Charge(order);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Order {OrderId} failed", orderId);
        throw ex; // stack trace reset here
    }
}

After: rethrow with throw;

catch (Exception ex)
{
    _logger.LogError(ex, "Order {OrderId} failed", orderId);
    throw;
}

When you want to add context: wrap, don't reset

If the caller needs a more meaningful exception, throw a new one and pass the original as the inner exception. The inner exception keeps its full stack trace, and your new exception adds the business context.

catch (SqlException ex)
{
    throw new OrderStorageException(
quot;Could not load order {orderId}", ex); }

Rethrowing later: ExceptionDispatchInfo

Sometimes you capture an exception in one place and rethrow it somewhere else, for example after cleanup or on another thread. throw; only works inside the catch block, so use ExceptionDispatchInfo.Capture(ex).Throw(), which rethrows with the original stack trace plus a marker showing where it was rethrown.

Checklist

  • Use throw; to rethrow inside a catch block
  • Never write throw ex; for the exception you just caught
  • Wrap with a new exception and pass the original as innerException when you add context
  • Use ExceptionDispatchInfo when rethrowing outside the catch block
  • Turn on analyzer rule CA2200 ("Rethrow to preserve stack details") so the build warns you

Find every throw ex in your repository

CodeInspector flags throw ex as rule CI0004 ("Use throw instead of throw ex") across a whole repository, with the file, line, and the one-line fix. Paste a GitHub URL or connect your repo at https://codeinspector.app. New accounts get 100 free credits, enough to scan real projects.

Related