Exceptions are events that disrupt a page's normal flow, a failed database query, a missing include file, dividing by zero. cftry lets code catch one of these when it happens and decide what to do next, instead of letting ColdFusion's own default error page take over. cftry itself is deliberately simple: it takes no attributes at all, its only job is grouping risky code together with one or more cfcatch blocks (covered in depth in the next lesson) that decide what happens if something goes wrong.
Learning Objectives
After completing this lesson, you'll be able to:
- Write a basic cftry/cfcatch block, and know cftry's real structural rules.
- Nest a try block around one specific risky step, without changing how the rest of a larger process is handled.
- Use cffinally for cleanup that must run no matter which step failed, or whether anything failed at all.
How cftry Fits In
Code That Might Fail
Exception Thrown?
cfcatch Handles It
cffinally Always Runs
A Basic cftry / cfcatch Block
<cftry>
<cfset result = 10 / arguments.divisor>
<cfcatch type="any">
<cfoutput>Something went wrong: #cfcatch.message#</cfoutput>
</cfcatch>
</cftry>cftry cannot stand alone with zero cfcatch blocks, at least one is mandatory. The next lesson covers cfcatch's own attributes and the exception object it exposes in real depth.
The Anatomy of cftry
cftry
no attributes
≥ 1 cfcatch
required
cffinally
optional
cffinally, if present, always runs last, regardless of what happened above it, whether the try succeeded, a cfcatch handled a failure, or the cfcatch itself hit a problem.
A Real Example: Nesting try Around Just One Risky Step
A checkout process has several steps, checking inventory, charging a payment method, creating the order record. If only the payment step can reasonably be retried, wrapping just that step in its own nested cftry lets it be handled differently from a failure anywhere else in the process.
Outer try: Whole Checkout
Inner try: Payment Step Only
Inner catch Handles It, or Propagates Out
<cftry>
<cfset checkInventory(orderItems)>
<cftry>
<cfset chargePaymentMethod(customer, orderTotal)>
<cfcatch type="application">
<cfset flash.message = "Your payment could not be processed, please try again.">
<cflocation url="/checkout/retry-payment" addtoken="false">
</cfcatch>
</cftry>
<cfset createOrderRecord(customer, orderItems)>
<cfcatch type="any">
<cfset flash.message = "Something went wrong completing your order.">
<cflocation url="/checkout/error" addtoken="false">
</cfcatch>
</cftry>A payment failure gets its own specific, retry-friendly message. A failure anywhere else (inventory, order creation, or a payment exception of a type the inner catch doesn't match) falls through to the outer catch's more general handling.
A Real Example: Guaranteed Cleanup With cffinally
<cftry>
<cflock name="inventory-update-#productId#" type="exclusive" timeout="10">
<cfset updateStockLevel(productId, quantityChange)>
</cflock>
<cfcatch type="any">
<cflog text="Stock update failed for product #productId#: #cfcatch.message#">
</cfcatch>
<cffinally>
<cfset removeFile(expandPath("./locks/#productId#.lock"))>
</cffinally>
</cftry>The lock file is removed whether the stock update succeeded, failed, or the catch block itself encountered a problem, cffinally is the one place that's guaranteed to run.
Common Beginner Mistakes
Writing a cftry block with no cfcatch at all
This isn't valid, cftry requires at least one cfcatch block. There's no way to have a try with only a finally and no catch.
Nesting a try block around an entire large process instead of just the risky step
Wrapping everything in one broad try means every step gets the exact same generic handling. Nesting a smaller try around just the step that genuinely needs different treatment keeps the rest of the handling specific and clear.
Assuming an inner catch's failure to match still stops the exception
If a nested try's catch blocks don't match the exception's type, it propagates outward to the next enclosing try's catch, it isn't silently swallowed just because it passed through an inner try first.
Best Practices
- Nest a try specifically around the one step that needs distinct handling, rather than wrapping an entire multi-step process in a single generic catch.
- Use cffinally for anything that must happen regardless of outcome, releasing a lock, closing a connection, deleting a temporary file.
- Keep the code inside cftry focused on the operations that can actually fail, not unrelated logic that happens to sit nearby.
Interview Questions
Does cftry itself take any attributes?
No, cftry has no attributes of its own. All of the configuration, the type being matched and so on, lives on its cfcatch (and optional cffinally) blocks.
Is a cftry block valid with zero cfcatch blocks?
No, at least one cfcatch is required. cffinally alone, without any cfcatch, isn't a valid cftry.
Why nest a try block around just one step of a larger process, instead of wrapping the whole thing?
It lets that one step be handled differently, a retry-friendly message for a payment failure, for example, while every other kind of failure in the process still falls through to a more general outer handler.
What happens if a nested try's catch blocks don't match the exception that was thrown?
The exception propagates outward to the next enclosing try's catch block, it isn't discarded just because it passed through an inner try that didn't handle it.
Summary
In this lesson, you wrote a basic cftry/cfcatch block, covered cftry's real structural rules (no attributes, at least one cfcatch required), nested a try block around a single risky step so it could be handled differently from the rest of a larger process, and used cffinally for cleanup guaranteed to run regardless of outcome.
What's Next?
The next lesson covers cfcatch specifically: its own attributes, the full set of built-in exception types, and the different fields each type actually adds to the caught exception.