TRY/CATCH Error Handling in SQL Server: THROW vs RAISERROR

Written by

in

TRY/CATCH Error Handling in SQL Server: THROW vs RAISERROR

Production T-SQL needs to fail gracefully, not just fail. An unhandled error in the middle of a multi-step operation can leave data in a half-finished state — TRY/CATCH, paired with transactions (covered in full in Chapter 9), is how you prevent that.

Two paths through TRYBEGIN TRYruns like normal codeno errorrest of TRY runserror! jumps ⚡BEGIN CATCHrest of TRY is skippedTHROW;keeps original errorRAISERRORlegacy, pre-2012THROW 50000,…loses the original!

The Pattern

BEGIN TRY
    UPDATE dbo.Product SET stock_qty = stock_qty - 10 WHERE product_id = 2;
    IF (SELECT stock_qty FROM dbo.Product WHERE product_id = 2) < 0
        THROW 51000, 'Stock quantity cannot go negative.', 1;
END TRY
BEGIN CATCH
    PRINT 'Error caught: ' + ERROR_MESSAGE();
    PRINT 'Error number: ' + CAST(ERROR_NUMBER() AS VARCHAR(10));
    PRINT 'Error line: ' + CAST(ERROR_LINE() AS VARCHAR(10));
END CATCH;

Code inside BEGIN TRY ... END TRY runs normally. The instant any statement in that block raises an error, execution jumps immediately to BEGIN CATCH ... END CATCH — the rest of the TRY block is skipped entirely, similar to try/catch in C#, Java, or Python, but with SQL-Server-specific error inspection functions.

THROW vs RAISERROR

THROW (2012+) Simpler syntax Correctly re-raises original error RAISERROR (legacy) Older formatting features Pre-2012 compatibility

THROW is preferred — it's simpler, and with no arguments inside a CATCH block, it correctly preserves the original error's number, severity, and state when re-thrown. Reach for RAISERROR only when you need its legacy formatting (%s/%d placeholders) or must support very old SQL Server versions.

-- Re-throwing the ORIGINAL caught error, unchanged, after logging it:
BEGIN TRY
    SELECT 1/0; -- deliberate divide-by-zero to trigger an error
END TRY
BEGIN CATCH
    PRINT 'Logged: ' + ERROR_MESSAGE();
    THROW; -- bare THROW re-raises the exact original error
END CATCH;
Common mistake: Calling THROW with your own custom message/number when you meant to just re-raise the original error for the caller to see. A bare THROW; (no arguments) inside CATCH re-throws exactly what was caught — THROW 50000, 'Something failed', 1; replaces it with a brand-new, less specific error that loses the original diagnostic detail.

The Error Functions Toolkit

Function Returns
ERROR_NUMBER() The error's numeric code
ERROR_MESSAGE() The human-readable error text
ERROR_LINE() Line number where the error occurred
ERROR_PROCEDURE() Procedure/function name, NULL if ad-hoc
ERROR_SEVERITY() Severity level (11-19 typical for handleable errors)
ERROR_STATE() A custom state number you can use to distinguish similar errors

All six are only valid inside a CATCH block — called anywhere else, they simply return NULL, since there's no error context to describe.

Nesting: A TRY/CATCH Inside a CATCH

BEGIN TRY
    UPDATE dbo.Product SET stock_qty = stock_qty - 10 WHERE product_id = 2;
END TRY
BEGIN CATCH
    BEGIN TRY
        INSERT INTO dbo.ErrorLog (error_message, logged_at) VALUES (ERROR_MESSAGE(), SYSDATETIME());
    END TRY
    BEGIN CATCH
        PRINT 'Even the error logging failed — this is genuinely bad, escalate.';
    END CATCH;
    THROW;
END CATCH;

This is a real, defensible pattern in production code: log the error to a table for later diagnosis, but wrap the logging itself in its own TRY/CATCH — you don't want a failure in your error-logging code to mask or replace the original error.

Practice tip: Trigger three different real errors on purpose — a divide by zero, a constraint violation, and a THROW with a custom message — and print all six ERROR_ functions for each inside a CATCH block. Seeing how the values differ across error types builds real intuition faster than reading the reference table above.

Enjoyed this?

Subscribe to get every new SQL Server lesson as soon as it's published, and share it with a developer who'd find it useful.

📡 Subscribe via RSS  | 
Share on X  | 
Share on LinkedIn  | 
Share on Facebook

Want the full structured course with quizzes, projects, and 10+ exercises per chapter? Check out SQL Server for Developers & DBAs, coming soon on this site.