DevLearningTools

MODULE 12 · LESSON 02

<cfcatch>

cfcatch in depth: its two attributes, the full set of built-in exception types, the specific fields each type actually populates, why catch block order matters, and a real multi-catch example (a user registration flow).

New lessons are added one at a time as the course gets built out — a graded quiz for each lesson is still on the way.

The previous lesson introduced cftry and a basic cfcatch. cfcatch itself only has two real attributes, type (which exception types it matches) and name (what variable the caught exception is stored in), but type is where the real depth is: CFML has a full set of built-in exception types, each exposing different fields on the exception it catches.

Learning Objectives

After completing this lesson, you'll be able to:

  • Use cfcatch's type and name attributes correctly.
  • Recognize CFML's built-in exception types, and read the specific fields each one adds to the caught exception.
  • Order multiple catch blocks correctly, and combine several of them in one real, multi-step scenario.

cfcatch's Own Attributes

AttributeMeaning
typeWhich exception type this block matches (defaults to any)
name (or variable)What variable the caught exception is stored in (defaults to cfcatch itself)
NOTE

Lucee always populates the default cfcatch variable regardless of whether name is set to something else, referencing cfcatch directly inside the block generally works either way.

The Built-in Exception Types

typeCatches
anyEvery exception type (the default if none is specified)
applicationExceptions the application layer itself raises
databaseA failed database operation
expressionAn invalid expression, like dividing by zero
lockA failed named or unnamed lock operation
missingIncludeAn included file that couldn't be found
objectAn error involving a CFC or COM/CORBA object
securityA security-related exception
templateA general ColdFusion page-processing exception
searchengineA Solr search engine exception
a custom typeA specific custom type string set by a throw/cfthrow elsewhere in the code

What's Actually on the Caught Exception

FieldAvailable onMeaning
messageEvery typeThe exception's diagnostic message, if one was provided
detailEvery typeA more detailed message from the interpreter, or set explicitly in a throw
typeEvery typeThe exception's type, matching what the catch block matched on
tagcontextEvery typeAn array describing the active tag/call stack at the moment it was thrown
sql / NativeErrorCode / SQLState / queryError / wheredatabaseThe actual SQL sent, the database driver's own error code and message, and any cfqueryparam values
ErrNumberexpressionAn internal expression error number
MissingFileNamemissingIncludeThe name of the file that couldn't be included
LockName / LockOperationlockWhich lock failed, and what operation (timeout, create, etc.) failed on it
ErrorCode / ExtendedInfocustom / applicationA custom error code and extended detail set explicitly when the exception was thrown
NOTE

cfdump(var=cfcatch) (or dump(e) in CFScript) is the fastest way to see exactly what fields a specific exception actually populated, rather than guessing from a general reference.

How Multiple catch Blocks Get Checked

Exception Thrown

type="database"?

type="application"?

type="any" (fallback)

NOTE

The first matching catch block runs, and only that one. A general type="any" block placed before a more specific one would catch everything itself, so the most specific matching block always needs to come first, with any as the final fallback.

A Real Example: User Registration, Three Different Failure Types

Registering a new user can fail in genuinely different ways that deserve genuinely different responses: the email is already taken (a database constraint), the submitted data fails validation (an application-level exception thrown deliberately), or something else entirely goes wrong.

Tag Syntax
<cftry>
    <cfif len(trim(form.email)) EQ 0>
        <cfthrow type="application" message="Email is required.">
    </cfif>

    <cfquery name="insertUser" datasource="myDsn">
        INSERT INTO users (email, passwordHash)
        VALUES (
            <cfqueryparam value="#form.email#" cfsqltype="cf_sql_varchar">,
            <cfqueryparam value="#hash(form.password)#" cfsqltype="cf_sql_varchar">
        )
    </cfquery>

    <cfcatch type="database">
        <cfif findNoCase("unique", cfcatch.message) OR findNoCase("duplicate", cfcatch.message)>
            <cfoutput>That email is already registered.</cfoutput>
        <cfelse>
            <cflog text="Registration DB error: #cfcatch.message# — SQL: #cfcatch.Sql#">
            <cfoutput>We couldn't complete your registration, please try again.</cfoutput>
        </cfif>
    </cfcatch>

    <cfcatch type="application">
        <cfoutput>#cfcatch.message#</cfoutput>
    </cfcatch>

    <cfcatch type="any">
        <cflog text="Unexpected registration error: #cfcatch.message#">
        <cfoutput>Something went wrong, please try again.</cfoutput>
    </cfcatch>
</cftry>
NOTE

The database catch checks cfcatch.message for a duplicate-key style error to give a specific, actionable response, and otherwise logs the real cfcatch.Sql that was sent alongside a generic message to the user, never exposing the raw SQL or driver error to them directly.

A Real Example: Catching a Missing Include Gracefully

Tag Syntax
<cftry>
    <cfinclude template="/modules/#sectionName#/panel.cfm">
    <cfcatch type="missingInclude">
        <cflog text="Missing panel template: #cfcatch.MissingFileName#">
        <p>This section isn't available right now.</p>
    </cfcatch>
</cftry>
NOTE

MissingFileName is specific to missingInclude, it names exactly which file couldn't be found, useful for logging even though the user only sees a friendly fallback message.

Custom Exception Types Are Hierarchical

A custom type set via throw/cfthrow can use dots to build a hierarchy, like "Payment.CardDeclined". A catch block listening for the broader "Payment" type also catches the more specific "Payment.CardDeclined", the same way it would catch "Payment.Timeout" or any other type nested under it, without needing to list each specific case individually.

Common Beginner Mistakes

Putting a general type="any" catch block before a more specific one

The general block matches first and the more specific one never runs. Order catch blocks from most specific to most general.

Assuming every exception has the same fields

message, detail, type, and tagcontext are universal, but fields like sql, ErrNumber, or LockName only exist on the specific exception types they apply to (database, expression, lock).

Checking cfcatch.message with a fragile string match for something more reliably identified another way

Checking a database error's message text for words like "duplicate" is a reasonable, real-world approach in the absence of a more structured signal, but it's still tied to the specific database driver's own wording, verify it against the actual database in use rather than assuming it's universal.

Exposing cfcatch.Sql or a raw driver error message directly to the end user

Log the real detail server-side, but show the user a generic, non-technical message, exposing internal query details is both unhelpful to them and a real information disclosure risk.

Best Practices

  • Catch the most specific exception type that's actually relevant, rather than reaching for any by default.
  • Order multiple catch blocks from most specific to most general, with any last as the fallback.
  • Use type-specific catch blocks to give genuinely different responses to genuinely different failures, not just as a stylistic choice.
  • Log the specific, technical fields (Sql, MissingFileName, and similar) server-side, while keeping what the user sees generic and non-technical.

Interview Questions

What are cfcatch's two own attributes, and what does each control?

type controls which exception types the block matches (default any), and name (or variable) controls what variable the caught exception is stored in (default cfcatch).

Name three fields available on every caught exception, and one that's specific to a database exception.

message, detail, and type are available on every exception. sql (the actual SQL statement sent) is specific to database exceptions.

Why does the order of multiple catch blocks matter?

The first matching catch block runs. A general type="any" block placed before a more specific one would catch everything itself, so more specific blocks need to come first.

How does a custom exception type's dotted hierarchy work?

A catch block listening for a broader type (like "Payment") also catches more specific types nested under it (like "Payment.CardDeclined"), letting one catch block handle a whole family of related custom exceptions.

Summary

In this lesson, you used cfcatch's type and name attributes, covered CFML's full set of built-in exception types and the specific fields each one adds, learned why catch block order matters and how a custom exception's dotted type hierarchy matches, and combined multiple type-specific catch blocks in real scenarios.

What's Next?

The next lesson covers the exact same capability in CFScript, try/catch/finally, along with rethrowing and chaining exceptions.