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
| Attribute | Meaning |
|---|---|
| type | Which exception type this block matches (defaults to any) |
| name (or variable) | What variable the caught exception is stored in (defaults to cfcatch itself) |
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
| type | Catches |
|---|---|
| any | Every exception type (the default if none is specified) |
| application | Exceptions the application layer itself raises |
| database | A failed database operation |
| expression | An invalid expression, like dividing by zero |
| lock | A failed named or unnamed lock operation |
| missingInclude | An included file that couldn't be found |
| object | An error involving a CFC or COM/CORBA object |
| security | A security-related exception |
| template | A general ColdFusion page-processing exception |
| searchengine | A Solr search engine exception |
| a custom type | A specific custom type string set by a throw/cfthrow elsewhere in the code |
What's Actually on the Caught Exception
| Field | Available on | Meaning |
|---|---|---|
| message | Every type | The exception's diagnostic message, if one was provided |
| detail | Every type | A more detailed message from the interpreter, or set explicitly in a throw |
| type | Every type | The exception's type, matching what the catch block matched on |
| tagcontext | Every type | An array describing the active tag/call stack at the moment it was thrown |
| sql / NativeErrorCode / SQLState / queryError / where | database | The actual SQL sent, the database driver's own error code and message, and any cfqueryparam values |
| ErrNumber | expression | An internal expression error number |
| MissingFileName | missingInclude | The name of the file that couldn't be included |
| LockName / LockOperation | lock | Which lock failed, and what operation (timeout, create, etc.) failed on it |
| ErrorCode / ExtendedInfo | custom / application | A custom error code and extended detail set explicitly when the exception was thrown |
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)
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.
<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>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
<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>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.