From ColdFusion 10 To ColdFusion 2021 - Preparing For Some Blog Love
UPDATE - Nov 3, 2021: When I first published this article, I was running on Adobe ColdFusion 2018. However, right after publishing it, I asked my hosting provider to bump me up to ColdFusion 2021. As such, I've updated the article to reflect this new jump in features.
This blog is hella old. It started back in 2006 on Macromedia ColdFusion MX 7. At some point, it was updated to ColdFusion 10, where it remained for the better part of a decade. Recently, I updated it to Adobe ColdFusion 2018. But, the underlying code is still hella old and in much need of some love and tenderness. I'd like to put some time into modernizing the internals. But, before I do that, I need to get a sense of what "modernization" even means when moving from ColdFusion 10 to ColdFusion 2018. This post is really a note to self that refreshes my brain as to what functionality is now available to me in the current Adobe ColdFusion 2018 (nay, 2021) install.
What follows is not an exhaustive list - it's a cursory look at the "What's New" documentation, paired down to the features that will likely hold value for my particular style of programming.
What's New in Adobe ColdFusion 11
getSafeHtml()
- sanitizing HTML using the OWASP AntiSamy project.ASIDE: My blog currently using the AntiSamy project directly with the JavaLoader project for loading JAR files on-the-fly.
Elvis operator (
?:
) for null coalescing.CAUTION: On the Modernize or Die podcast, Brad Wood is constantly railing against the Elvis operator in Adobe ColdFusion for being so buggy that he can't even really use it. As such, I'll approach this with caution optimism. It's definitely a feature that I use all the time in Lucee CFML.
Member functions on native data-types.
Proper key-case preservation when serializing native data-types as JSON (JavaScript Object Notation). This has long been a massive pain in ColdFusion, making JSON almost unusable in many ways.
Full support for ColdFusion tags in
CFScript
block:withoutbody();
withbody() { ... }
cf_withoutbody();
cf_withbody() { ... }
prefix:customtag();
prefix:customtag() { ... }
ASIDE: I love that Adobe ColdFusion supports prefix-based ColdFusion custom tags in
CFScript
. At this time, Lucee CFML doesn't support this. And, I love using custom tags, especially for my HTML Email DSL.Built-in Functions (BIFs) as first-class citizens. Meaning, the function references can be passed-around as references.
queryExecute()
- executing SQL queries inCFScript
. When possible, I want to use script-based components. This is not as good as using tag-islands in Lucee CFML; but, I'll get used to it.More Array methods,
arrayMap()
,arrayReduce()
.More Struct methods,
structMap()
.
What's New in Adobe ColdFusion 2016
Safe navigation (
?.
) operator to help avoid null-pointer exceptions in a deep-object expression.ASIDE: This is often used in conjunction with the Elvis operator to provide a default for complex expressions,
( a?.b?.c ?: "default" )
.Ordered Structs / Collections using
[:]
or[=]
syntax. This is really helpful when dealing with MongoDB collections in which the order of keys is actually meaningful to the underlying document query.passArrayByReference
- application setting that allows Arrays to be passed-by-reference. Historically, arrays have been passed-by-value in ColdFusion (which is less performant).searchImplicitScopes
- application setting that limits the number of scopes to be searched when referencing un-scoped variables.More Query methods,
querySort()
,queryFitler()
,queryMap()
,queryReduce()
.valueArray()
- getting a set of query column values as an array.item
attribute inCFLoop
that stores the value in an array loop. Can be used in conjunction withindex
to get both the value and index.encodeFor
attribute inCFOutput
tag. This automatically applies the given encoding to all interpolation expressions in the output block.replace()
can now take a closure.
What's New in Adobe ColdFusion 2018
Negative indices on Arrays,
[-1]
to access last value.Array slices,
[1:6:2]
which grabs indices 1-through-6, incrementing the step by 2 each time.Negative indices on Strings,
[-1]
to access last character.String slices,
[1:6:2]
- works the same as Array slices.More Array functions,
arrayFirst()
,arrayLast()
.More Query functions.
enableNULLSupport
- application settings that enables fullNULL
support. This allows you to assignNULL
to variables.Proper data-type preservation in Queries. This is particularly important for JSON support.
Fat-arrow syntax (
() => {}
) for closures.Invoke array items as functions,
myArray[1]()
.Chaining data-type member functions.
Instantiating ColdFusion components at a given path using
new component(pathToCFC)
. This will be super helpful as I've had to jump through many hoops in the past to invoke ColdFusion components that live above the contextual directory.Creating Java objects using
new java(className)
.Named parameter support for Built-In Functions (BIFs).
runAsync()
for Future-based asynchronous programming.
What's New in Adobe ColdFusion 2021 via Charlie Arehart
BCrypt and SCrypt Password hashing functions.
Parallel iteration for Structs and Arrays. I'm super excited about this one. I use this feature in Lucee CFML all the time to download files in parallel. It's a huge value-add!
IIFE (Immediately Invoked Function Expressions) - more than anything, this just indicates a modern advance in the syntax capabilities of the language.
Spread operator for Functions, Arrays, and Structs - allows an iterable collection to be applied / merged into another collection.
ASIDE: In addition to spreading a Struct into another Struct, it looks like you can also merge structs by simply including one inside another one as a non-key-value entry.
Rest operator - allows the capture of variadic function arguments into an iterable variable.
Identity operator (
===
) - allows the comparison of both value and type in an equality expression.Case-sensitive struct notation (
${ key:value }
).ASIDE: I believe if you have "preserve case" enabled, then you don't even need this notation.
Destructuring Arrays and Structs - honestly, I'm not a fan of destructuring. I find this style of code to be hard to read, and tends to over-tax the "compiler" in my head. The same is true with the new "property shorthand" syntax. It all falls under the "too clever" category for me. That said, it's good to know that it's here.
Dynamic
switch
statements - this can be great when the case values point to expressions; though, it looks like the supported functionality is way more dynamic than I would recommend using.Query
returnType="array"
- returns an Array-of-Structs instead of a Query object.New Array methods,
ArrayPush()
,ArrayPop()
,ArrayShift()
, andArrayUnshift()
- bringing things closer to JavaScript syntax, which is always nice!Base64Url
support for encoding / decoding binary values - this is becoming a popular choice for anything that needs to be passed safely via the URL.Writing Java code right inside our CFML - I'm super curious about this feature. One of the things that has held me back over the years is not feeling confident in extending ColdFusion code with custom Java classes. This might change everything!
ASIDE: It seems the one thing that Adobe ColdFusion is still lacking when compared to Lucee CFML is the ability to load JAR files on-the-fly via the
createObject()
function. It looks like I'll still be using the JavaLoader project to do that in Adobe ColdFusion 2021.
Again, this is not meant to be an exhaustive list of features added in the various ColdFusion releases - this was just a subset of the features that appealed to me (and will likely be helpful as I incrementally update my blog architecture). A lot of my blog code is still tag-based. So, one of the main improvements that I hope to make is to move as much of it as possible to CFScript
. Which, should be relatively easy with the full-tag support in the language.
As I was Googling for this stuff, I stumbled upon a page on the CFDocs site that lists features improvements by version. And, of course, Charlie Arehart always has a wealth of information regarding the various ColdFusion releases and their hidden gems.
Reader Comments
Currently talking to my hosting provider to see if I can upgrade to Adobe ColdFusion 2021. I mean, might was well make the biggest jump I can, right?!
Great list. I love how far CF has come. Two questions...
@Chris,
So, I use Hostek. And, when I was doing the upgrade from ColdFusion 10 to 2018, I did ask if I could change over to Lucee since that's what I use at work. They said that they do have Lucee hosting and they support it; but, that their team was much better at managing Adobe ColdFusion. So, I just decided to keep on ACF. Plus, I figured it might be nice to have two platforms in my life - for a bit of variety 😜
Woot! Hostek is gonna upgrade me to ACF 2021. So, when that is done, I'll update this list with whatever new goodies come in 2021.
Great summary Ben. Looking forward to the upgrading adventure :)
@Gavin,
Boo ya! I'm sure it will make for some interesting fodder as well.
Sounds good! I've always been happy with Hostek. The great thing about moving to Lucee is that you can get more resources for less $$, but that might not be a huge selling point in your case.
@Chris,
Wait, are you saying that you have Lucee running on Hostek?
TEST TEST TEST -- just upgraded to ACF 2021, making sure emails work. Sorry for the spam.
@All,
Now that this blog is running on Adobe ColdFusion 2021, I've updated the blog post to reflect some of the new features that I want to explore in my code.
That said, I'm still missing the ability to load JAR files on-the-fly like you can do in Lucee CFML's
createObject()
method. And, of course, tag islands is still a huge feature gap that revolutionized my CFML code.Tag islands are the coolest thing I've seen that I'd love to incorporate into my code. I'd love to migrate to Lucee...not just for this feature either 🤓
@Chris,
💯💯💯 I'm going to have to learn to like
queryExecute()
to run SQL in CFScript. But, I'd so much rather be using<cfquery>
and<cfqueryparam>
inside a tag island 😭Ben, really glad to see the addition of 2021. But as for the lack of the ability to name a jar on createobject/cf object, I hope you and readers are at least aware that you can load jars on a per-app basis (this.javasettings.loadpath), rather than rely otherwise solely on the old notion of having to load jars into cf's classpath.
That's a great half-way point, until Adobe may take on the other feature. Many have leveraged the app settings approach since it came out in cf10 (2012), which is indeed about the same time PRs in the javaloader github repo started to tail off substantially. But it seems worth at least mentioning, as some may NOT know about it.
@Charlie,
You raise a good point. I think in the past, I've shied away from the app-level Java settings only because I've run into version conflicts between different libraries (since they all go into the root Class Loader, from my understanding). But, it's definitely good for something that truly stands on its own, such a custom Java classes that you create for your own project.
I'm not sure about that, Ben. There would certainly be no impact outside the application (that's the main point of it being an app setting), but if you may mean that the classes/jars in the "root classloader" would conflict with those named in the javasettings.loadpaths (I forgot the "s") in my last comment), I will note that there's another setting in that javasettings for loadColdFusionClasspath, a boolean.
If instead one wanted to use both of those (classes in the named new classpath AND classes from CF's own classpath), whether in your calling them with createobject/cfobject or by CF's own internal loading of java classes to run your code, I suppose there could be conflicts, sure. (I suspect we could dig in to find which get loaded FIRST in the classpath within the app. I'd expect the one discussed here would take precedence, but that may not be enough to prevent some conflicts.)
BTW, for any interested in still more on this, I should have added that the docs for this (and other things that javaloader did, like providing a dynamic proxy) are at https://helpx.adobe.com/coldfusion/developing-applications/using-web-elements-and-external-objects/integrating-jee-and-java-elements-in-cfml-applications/enhanced-java-integration-in-coldfusion.html
Oh, and thanks for the mention (in the section title for your CF2021 "new feature" section). I gather you got that info from my talk, "hidden gems in cf2021". And of course you had acknowledged my hidden gems talks in your original version of this post--and thanks for that, of course.
Yep, folks interested in still more about differences in the various recent CF versions will find that I covered most of these (and perhaps some more coding changes) in those talks, which are all at carehart.org/presentations, specifically. Note that in those talks I also discuss far MORE than just coding changes, but I understand why that's the limit of focus in Ben's post here.
And thanks for it!
@Charlie,
Sorry for my lack of clarity 🙃 what I meant by "conflict" is that I might want to load two different 3rd-party libraries that respectively have dependencies that conflict with each other. Imagine I have 2 libraries that both use
com.bennadel.widget
; but, with two different version that are incompatible:com.bennadel.widget@2.0.1
com.bennadel.widget@3.0.1
I believe that If I try to load both of those version in the Application-specific JAR paths, only one of them will actually be loaded. And I might end up making the wrong version available to one of the dependencies.
Oh. Well, yeah. I guess so. :-) Sounds like a bit of an edge case, but if you or others ever trying it may find it doesn't work as expected, that would be something to certainly raise to Adobe at tracker.adobe.com (where example code and even java class files can be uploaded as attachments).
While some think of tracker as a place "where bug reports go to die", the fact is that each CF update (every few months) tends to have a few dozen bug fixes. So they really do look to tracker, and I can attest to seeing recent activity on many reported bugs. (Granted, someone may find that THEIR bug report languishes--as I can attest has happened to me also on occasion.)
As the saying goes, "it's better to light one candle than to curse the darkness". :-)
@Charlie,
You might be right - maybe it is more of an edge-case than I think it is. I feel like I have been burned by conflicting "http client" libraries somewhere. Or "commons logging" or "commons connection pool" versions. But, maybe I should just try to use the app-level JARs until I can't ; and then, worry about it then.
I would love to be able to get rid of as many dependencies as I can.
Seeing that you just upgraded to CF2021, dropping a tip if you run into Java errors such as "java.lang.reflect.InaccessibleObjectException" that I got when I tried out your GetTextDimensions function after likewise upgrading to CF2021. The JVM needs a couple arguments to get the function working again:
--add-opens=java.desktop/sun.java2d=ALL-UNNAMED --add-opens=java.desktop/sun.font=ALL-UNNAMED
Something about java objects not being public, it's all mumble-jumble to me but I was able to figure out the means for adding jvm args to work around it.
I couldn't find a blog post on issues upgrading to CF2021 but this one is close. One topic is weird issues that crop up with going from old to new, one of which is a "java.lang.reflect.InaccessibleObjectException" error that I received when checking out your GetTextDimensions function. It seems that some Java objects are no longer public trying to access it causes an error. In the case of your CFC, it was on the following line when accessing GetGraphics()
<cfset LOCAL.Graphics = ImageGetBufferedImage( LOCAL.Image ).GetGraphics()/>
The solution is to add the following JVM arguments to open up the java2d & font areas:
--add-opens=java.desktop/sun.java2d=ALL-UNNAMED --add-opens=java.desktop/sun.font=ALL-UNNAMED
Sorry for the dupe stuff, recomposed the message but forgot to erase the old! Scratch out "I couldn't find a ..." and after
@John,
Good tip! I think I either ran into that or something similar. After I upgraded, the site wouldn't load (an error was being thrown in the
onApplicationStart()
event-handler when trying to instantiate theJavaLoader
component. I had to go into the CFIDE and uncheck the security option for "Disable access to internal ColdFusion Java components". It sounds like this may be related to what you're talking about. As with you, this is all sort of mumbo-jumbo to me 🤪Randomly, I just stumbled upon the fact that you can't call
.resize()
on an array-literal:[].resize( 10 )
But, at least you can call it on the result of
arrayNew()
:arrayNew( 1 ).resize( 10 )
It looks like maybe you can't call methods on array literals at all. Meh - stuff like this should just work by now. This languages is like 25 years old 😨 Anyway, small venting... back to the coding!
The language is mature, but member functions are still relatively new. Still...I 💯 agree "stuff like this should just work" for sure.
Also, update... I'm starting to prefer
queryExecute
over<cfquery>
. It just took me a little practice to get use to it.@Chris,
I've been getting used to the
queryExecute()
function. I use it on this blog because I've been converting all my non-view code to ColdFusion Components. It's growing on me a bit. But, I still miss using tag islands in Lucee CFML.So, I've given up on
queryExecute()
. I've just gone back to writing my data-access layer (DAL) using good-ol' tags.It's more verbose, to be sure. But, ultimately, the value-add of being able to use the
CFQuery
tag (for me) far outweighs the increase in "cruft" around the component and function tags.Interesting 🤔
I've fully embarrassed queryExecute myself, but I get it. Now, even when I could write a tag-based query, I trend towards script. The fact that we have the option to chose our own Dx is a strength of CF in my opinion.
@Chris,
I 💯💯 agree! There are a lot of people in this world that thing a programming language should only have the one way to do things. But, if I can quote the Handmaid's Tale:
Everyone is different and everyone has things that either connect or don't connect. I absolutely love that ColdFusion allows for so much flexibility. We each get to figure out how to write the code that makes us the most happy!
I've run into an issue with CF2021 that has me confused and I'm wondering if anyone knows what is happening or how I could debug this. It has to do with case sensitive structs.
I'm implementing SAML using the CF tags InitSAMLAuthRequest() and ProcessSAMLResponse(). The response is passed into a CFC that handles the authentication and login.
I dump the response and everything looks as expected, but when I try to access keys within the structure, it told me it couldn't find them. I was stumped and tried a bunch of stuff, no luck. As per our naming convention, I was attempting to access them using lowercase as we always have. I finally tried referring to the keys in uppercase and it worked.
The keys were in uppercase in the dump, but it has never mattered before - regardless of upper or lowercase, I could reference the key in lowercase and it would work.
The "Preserve case for Struct keys for Serialization" option is off in the administrator. All tests were on Windows 11 using JVM 13.0.2. CF2021 Update 12.
I ran some tests against the structure returned by ProcessSAMLResponse(). Running samlresponse.isCaseSensitive() returns NO. I cannot figure out why referencing the keys in lowercase does not work but uppercase does.
I have not run into this issue elsewhere. The vast majority of the structures in the code are defined by us and always referenced in lowercase. With this, CF generates the structure and hands it back to me.
Thoughts from anyone on how to debug this or if there is a switch I have flipped somewhere?
@Doug,
I haven't personally tried the SAML features in ColdFusion yet; but, as of CF 2021, you can create a case-sensitive struct:
structNew( "casesensitive" )
I've never actually tried this, as I don't know what the real value-add would be in normal day-to-day code. But, perhaps this is what the CF implementation is doing under the hood with SAML for some reason?
That's just my best guess, though. Seems strange!
Hey Ben - thanks for the response. I knew of the case sensitive structs but I also haven't had a use for them yet (I can think of some JavaScript reasons that would eliminate some toLowerCase nonsense, but that's a topic for another time). I read this article which explains the new structure features, and is where I found the function to output if the struct is case sensitive.
https://coldfusion.adobe.com/2020/11/case-sensitive-structs/
When I run isCaseSensitive() against the response CF hands me from the SAML call, it tells me it is NOT a case sensitive struct. If it isn't, I don't know why it is forcing me to use matching case to access the keys.
My code is working because I'm using matching case...it's just...weird. And bothering me. 🙃
I also should have stated that I created a couple of case sensitive structs in a test and ran the same isCaseSensitive() metadata checks against those. Those did return YES as expected, but the struct that is forcing me to use matching case to access the keys says NO. Sure seems like they are case sensitive!
@Doug,
It is really weird! My only thought is that the underlying data structure that ColdFusion is using for the SAML control flow is "Struct-like", but is perhaps some specialized version that the decision functions don't know how to handle. I only suggest this because something similar happened to me with a MongoDB driver a few years ago - the MongoDB query was returning a Struct; but, whenever I went to call member-methods on it, it would error. Apparently, there are many different kinds of "structs" in Java-land; and, CFML doesn't always know to map them to ColddFusion-structs.
But yes, agreed, it would bother me too if I didn't understand why I needed to do something.
Unfortunately, the BIFs do not generically work in this way as you might hope, even with ColdFusion 2023, but especially with ColdFusion 11 where they initially claim. See CF-4202219.
And even the ones that do work may not have been added until a later version. For example,
compare
andcompareNoCase
were supported started with ColdFusion 2021 because it was brought up as an issue (in 2017) -- see CF-4198573.@Kamasama,
Very interesting. I never actually got around to trying these mechanics. To be honest, I don't normally love passing around function references since the function signatures rarely match exactly what I want to do with them. Plus, if you try to pass around a function reference in something like a CFC, you lose the association to the current
variables
scope (unless you pass around a array-function reference).One comment (from Vijay) that I wanted to pull out from the first ticket you posted, which I think is some good insight for people in general:
This is a great reminder that the language can expose things that have a good developer experience (DX) - that is, that they are easy to use and hide a good deal of complexity. And, sometimes that complexity has to be massaged a bit at compile time.
Thanks for pointing this out.
@Ben,
I think it was a mistake to include that first bug since what I was referring to is bigger than that. The main issue with them is more exemplified in the second bug. Maybe they can technically be passed and that qualifies them as first-class, but there is some arbitrary limitation not allowing BIFs to be used in the same way as you can a UDF, even when they should work. And I even commented that on that bug 4 years ago.
An example is
myArray.filter(isNumeric)
results in the error "Cannot cast coldfusion.runtime.CFPageMethod to coldfusion.runtime.UDFMethod" being thrown.isNumeric
qualifies as a function that should be usable for a filter, but they just don't generally let BIFs be used for callbacks.@Kamasama,
In general, I tend to shy away from using native BIFs in this manner due to the difference in number of arguments. Meaning, in your example,
isNumeric()
is a unary function that only accepts one argument. But, when you pass a callback to.filter()
, the callback actually receives three arguments (element
,index
,array
). I've never quite been comfortable with the passing 3 args to a function that only accepts 1.To be clear, I'm not passing judgement—I'm only sharing why I've never quite wanted to try this.
@Ben,
But
ArrayFilter
is documented such that the other two parameters are optional. For me, I never implement those functions to accept arguments I will never use, and I rarely use the index let alone the collection itself which I don't recall ever using. I would say it's even bad practice to declare them and most linters will warn about unused parameters.That limitation I mention can of course be worked around with something like
myArray.filter((item) => isNumeric(item))
which is still a function that only accepts one argument, but is acceptable simply because it is a UDF and thus requires unnecessary verbosity.Putting aside preference, I think it's a failing of the language. Even if a BIF perfectly fit the number and kind of arguments it expects, it is throwing the exception simply because it is a BIF. Since BIFs are global, what is even the point of passing them if they can't be used in this way? It seems like a checklist item that they are technically first-class even though that doesn't mean much in practice.
@Kamasama,
Yes, I tend to agree. Preferences aside, it would seem intuitive that any Function—built-in or custom—could be passed around. Function as a "first class" citizen is quite common in languages these days.