Regression/Breaking change in arguments scope and nullSupport in Lucee 6.1+

Hello everyone,

I am currently migrating an application from Lucee 5.4 to Lucee 6. While the step to 6.0.4 was successful, I’ve encountered two significant issues starting from version 6.1.x related to Null handling and how the arguments scope is populated.

#1

In Lucee 5.4 and 6.0, with nullSupport=true, an optional argument defined in a function signature was always present in the arguments scope (as null), even if not passed by the caller. Starting from Lucee 6.1, with nullSupport=true, the key is completely missing from the scope if the argument is not provided.

public void function myFunc( Numeric memberId ) {
    // Throws error in 6.1+ if called without args with with nullSupport=true
    var resolve = arguments.memberId; 
}
myFunc();

Error: The key [MEMBERID] doesn't exist in the arguments scope.

Testing Results:

  • 5.4.8.2 / 6.0.4.10: Success (Key exists/is null).
  • 6.1.0.243 and above (including 7.0.2.106): Error.

I’ve noticed this behavior is strictly tied to the nullSupport setting:

  • With nullSupport=false, the code works (legacy behavior).
  • With nullSupport=true, the key is missing. (I use this)

Question: I can’t tell if this is an intentional change or a bug. I can’t find anything about it in Lucee’s breaking changes. Should I start using safe navigation (?.) for optional parameters?

#2

There is a discrepancy in how null is serialized when nullSupport is disabled.

// Case A: nullSupport=true
cfapplication( action="update", nullSupport=true );
s = { "name": "John", "middleName": null };
echo( serializeJSON( s ) ); // Result: {"name":"John","middleName":null} -> CORRECT

// Case B: nullSupport=false
cfapplication( action="update", nullSupport=false );
s = { "name": "John", "middleName": nullValue() };
echo( serializeJSON( s ) ); // Result: {"name":"John","middleName":null} -> EXPECTED: {"name":"John"}`

According to docs, with nullSupport=false, the key should be omitted from the JSON, as null was not a native type in legacy CFML. This looks like a bug in the new Lucee 6.1+.

Thank you for your support! :heart:

Lucee Version: 6.1.2+47 (CommandBox)

serializeJSON does not omit nullValue() keys when nullSupport=false

https://luceeserver.atlassian.net/browse/LDEV-6185

but after thinking about this for a while, this is where i ended up.

The current behaviour (serializing null keys as null in JSON) is arguably correct, and the proposed test expectation is wrong.

The core issue is a semantic tension between CFML and JSON:

  • JSON semantics: null is a first-class value. {"name":"Pothys","middleName":null} — the key exists, its value is null.
  • CFML semantics (nullSupport=false): nullValue() means “doesn’t exist”. structKeyExists(data, "middleName") returns false, even though the data is still held in the struct internally.

If serializeJSON stripped null keys to match CFML’s structKeyExists behaviour, you’d silently lose data when round-tripping JSON through Lucee. Any external JSON payload with null values would have keys dropped during processing — that’s far worse than the current inconsistency.

The real oddness here is structKeyExists — it lies about what’s in the struct when nullSupport=false. The data is demonstrably there (proven by serialization), CFML just pretends it isn’t. That’s a long-standing CFML quirk, not a serializeJSON bug.

Behavior change — optional arguments absent from arguments scope when nullSupport=true

https://luceeserver.atlassian.net/browse/LDEV-6188

Positional calls (your exact example) — optional args without defaults have never been added to the arguments scope when nullSupport=true. This is broken in 5.4, 6.0, 6.2, and 7.0. You may have had default values on your args in production which masked this.

argumentCollection calls — this IS a genuine 6.1+ regression. In 5.4/6.0, a dialect guard accidentally checked config-level nullSupport instead of application-level, which happened to produce the correct behavior. When dialect support was removed in 6.1.0.10, the accidental fix was lost.

The fix for both is straightforward — remove the if (!fns) guard in UDFImpl.defineArguments() so optional args are always added to the scope. ArgumentImpl.containsKey() already handles the nullSupport distinction downstream.

Plan

Fix would be likely in 6.2.7 and 7.0.4 as 6.2.6 and 7.0.3 are quick follow up bug fix releases, without any somewhat breaking changes / fixes

1 Like