Skip to main content
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Brad Wood
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Brad Wood ( @bdw429s )

Showing An Error Message In The OnError() Method In Application.cfc In CFML

By on
Tags:

One of the many great things about the ColdFusion (CFML) runtime is that it natively provides an event-based framework in the form of Application.cfc. This ColdFusion component allows us to tap into events such as application, session, and request initialization. It also allows us to define a global error handler via the onError() method. This method, however, is tricky to use because it means โ€” by definition โ€” that the error in question was not handled properly (by lower-level code). This makes the state of the request very unpredictable; which means that we need to introspect the request before we attempt to output any error message.

The onError() method can be called at any time, in any phase of the ColdFusion application processing. This might be in the onApplicationStart() event-handler, before your code has been fully bootstrapped; or, it might get called due to some null variable reference deep inside a CFInclude template execution. In some cases, content has already been rendered to the client, which means we have fewer options in how we handle our errors; and, in other cases, nothing has been flushed to the client yet, which means we have more options.

In order to figure out where we stand, we need to introspect the state of the underlying Java servlet response. This object can be accessed via:

getPageContext().getResponse()

Once we have our underlying servlet response, we can check to see if the response has been "committed":

getPageContext().getResponse().isCommitted()

A response is considered "committed" when its HTTP headers have already been sent from the server down the client. Once a response is committed, changing aspects of the request can be tricky - it's probably best to just log the error and abort the request.

If, on the other hand, the response has not yet been committed to the client, then we have carte blanche: we can completely reset the output buffer and render a user-friendly error message.

To see this in action, I've set up a tiny ColdFusion application that has one template - index.cfm - that always throws an error. Based on the presence of a URL flag, this template may or may not call CFFlush prior to the error to commit the response to the client:

<!---
	If the COMMIT flag exists in the URL, then let's flush the response to the client.
	This will change the state of the application by the time we throw the error below
	(which will affect how our application's onError() event-handler can work).
--->
<cfif ( url.commit ?: false )>

	<!---
		CFFlush sends the HTTP headers in the response and starts streaming content to the
		client as the given number of bytes become available.
	--->
	<cfflush interval="1" />

</cfif>

<!--- ------------------------------------------------------------------------------ --->
<!--- ------------------------------------------------------------------------------ --->

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>

	<h1>
		ColdFusion is the Cat's Pajamas!
	</h1>

	<p>
		ColdFusion's combination of tag-based and script-based syntax makes it both
		extremely flexible and incredibly powerful. It integrates seamlessly into your
		HTML templates while also presenting with the familiarity of many script-based
		languages (such as JavaScript). Oh, except it also has the full power of the JVM
		under the hood.
	</p>

	<p>
		That's <em>kablamo!</em>
	</p>

	<!--- We're going to THROW AN ERROR MID-WAY through our template processing! --->
	<cfthrow
		type = "Boom"
		message = "Something went boom in the night."
	/>

</body>
</html>

As you can see, the CFThrow tag comes at the very bottom of our ColdFusion template. Which means that it may - or may not - occur after content has started streaming down (over the network) to the client.

To handle this error, let's create an Application.cfc ColdFusion application framework with an onError() event-handler. This onError() method will attempt to output a user-friendly error message. And, depending on the state of the request, this may be a full-page rendering; or, it may just be a tiny snippet followed by an abort.

component
	output = false
	hint = "I define the application settings and event handlers."
	{

	// Define the application settings.
	this.name = "ResponseTesting";
	this.applicationTimeout = createTimeSpan( 1, 0, 0, 0 );
	this.sessionManagement = false;
	this.setClientCookies = false;

	// ---
	// LIFE-CYCLE METHODS.
	// ---

	/**
	* I handle any uncaught error that is thrown within the ColdFusion application. This
	* error might come from anywhere: from a template execution or even from within the
	* bootstrapping of the application itself (ie another event handler in this very CFC).
	*/
	public void function onError(
		required any error,
		required any eventName
		) {

		// TODO: Replace with "real" logging technique.
		systemOutput( error, true, true );

		// Since this error might have been thrown from anywhere in the application and at
		// any point in the control flow, we have no idea what state the application is
		// in. As such, let's check to see if the HTTP response headers have been
		// committed. If they have been committed, the HTTP response is in an extremely
		// unpredictable state. Our best bet is just to abort the response altogether.
		if ( isResponseCommitted( eventName ) ) { 

			echo( "Sorry, an unexpected error has occurred." );
			abort;

		}

		// If we made it this far, then the HTTP response has not yet been committed to
		// the client. This means that we still have the opportunity to "reset" it and
		// render a (more) user-friendly error page.
		header
			statusCode = 500
			statusText = "Server Error"
		;
		// Reset the output buffer.
		content
			type = "text/html; charset=utf-8"
		;
		// Render a user-friendly error page.
		include "./server-error.cfm";

	}

	// ---
	// PRIVATE METHODS.
	// ---

	/**
	* I determine if the underlying Servlet HTTP response headers has been flushed to the
	* client (which would mean that at least some sense of output has already been
	* generated by the current request and the response cannot be reset).
	*/
	private boolean function isResponseCommitted( required string eventName ) {

		// CAUTION: There is no viable page context in the "end" event handler. As such,
		// we'll consider those error contexts to be "committed".
		return(
			( eventName == "onApplicationEnd" ) ||
			( eventName == "onSessionEnd" ) ||
			getPageContext().getResponse().isCommitted()
		);

	}

}

As you can see, if isResponseCommitted() returns a true, indicating that the HTTP Headers (and possibly content) have already been flushed to the client, the best we can do is abort the request. However, if isResponseCommitted() returns false, then we can override the HTTP status codes, reset the output buffer, and render a full-page high-fidelity error message.

ASIDE: If we don't override the HTTP status codes, ColdFusion uses 200 OK for caught errors. In order to adhere to standards, we want to return a non-2xx status for any page that can't uphold its contract.

Now, if we try to render our index.cfm template with an executed CFFlush tag, we get the following output:

A ColdFusion template, mostly rendered, with a small error message the bottom.

As you can see, when CFFlush is called before our error is thrown, there's not too much we can do - our onError() handler just appends a small message to the current page and aborts the processing.

If, on the hand, we render our index.cfm template without calling CFFlush first, we get the following output:

A ColdFusion stand-alone error template that was rendered after the content buffer was reset.

As you can see, since none of the HTTP headers were flushed to the client by the time the onError() event-handler was invoked, ColdFusion was able to completely reset the output buffer, override the HTTP status code, and render a stand-alone error template.

I'm kind of surprised that ColdFusion doesn't have a built-in function for checking the status of the current response. Or, maybe there's a better way to do this and I just don't know about it? In any case, I would prefer to handle errors in a non-global way (typically in a controller layer); but, that's not always possible. And, when we do handle an error globally, we just have to keep in mind that the response is in an unpredictable state.

Want to use code from this post? Check out the license.

Reader Comments

167 Comments

Error handling is one of those deep rabbit holes I've yet to wander down fully. I believe my knowledge to still be fairly surface and underutilized, so I very much appreciate posts like this. I don't have a lot of opportunity to deepen (and widen) my knowledge in this area.

15,377 Comments

@Chris,

1000% I've been doing this a long time and I still don't have an approach that I truly love. Every time I start something new, I feel like I'm continuing to poke and rethink the way error handling happens. Seems so odd that this is so challenging ๐Ÿ˜ฐ not sure if that's just intrinsic complexity of the topic; or, if we're just making it more complicated than it has to be? No idea.

15,377 Comments

This is kind of beyond the scope of this article, but it might be worth mentioning that an error that occurs in the CFThread tag does not get caught by the onError() handler in the Application.cfc. According to Zac Spitzer, thread errors get logged to the thread.log file; but, that's not something that your application can react to. I usually just wrap my thread body in a try/catch that logs any caught errors.

Post A Comment — I'd Love To Hear From You!

Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.
I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel