Heads up: Struct key ordering changes in Lucee 7.1 — check your code & tests

Hi everyone,

Wanted to give everyone a heads up about a change in Lucee 7.1 that’s already caught out a couple of major projects.

We are close to our first RC for 7.1

What changed

In 7.1, we replaced Lucee’s internal ConcurrentHashMap implementation with a modern wrapper around Java’s java.util.concurrent.ConcurrentHashMap (LDEV-5908). This is a significant performance win — up to 2.7x faster under high contention — but it changes the iteration order of keys in regular (unordered) structs.

Regular structs have never guaranteed key ordering in CFML. But in practice, the old implementation happened to produce a consistent order, and a lot of code ended up depending on that accident.

What breaks

If your code does any of the following with regular structs, it may produce different results in 7.1:

  • Comparing serializeJSON() output as strings (e.g. in tests or caching)
  • Generating hashes or cache keys from serialized structs
  • Building strings by iterating struct keys and expecting a stable order
  • MockBox $args() matching on nested struct arguments

We’ve already seen real-world breakage in:

  • PresideCMS — 11 test failures across multiple suites, plus a production concern where FK constraint names are derived from serialized struct output, potentially triggering unnecessary DB migrations on upgrade (PRESIDECMS-3264)
  • TestBox/MockBox$args() matcher fails when nested structs have different insertion order, because normalizeArguments() falls through to struct.toString() for nested values (TESTBOX-448)

What to do

  • Use ordered structs where key order matters: structNew("ordered") or [:] notation
  • Compare deserialized structs instead of raw JSON strings
  • Sort keys when generating deterministic output (cache keys, hashes, markup)
  • Review your test suites — if tests pass on 7.0 but fail on 7.1, struct ordering assumptions are the most likely culprit

Full details in the breaking changes doc:

If you hit anything related that isn’t covered above, please let us know.

4 Likes

Thanks for the great write up!

Cheers!

One thing which can be confusing is cfdump renders structures sorted by keys

1 Like

As this can be a bit confusing, we have added a backwards compat flag, so you can also use the old implementation to make triaging problems easier

Also found and solved a deserialization bug in 7.1 due to class name changes along the way.

Anyone who has been testing with 7.1 before 7.1.0.5 might need to purge their caches, including sessions, but anyone going from 7.0 to 7.1 should see zero problems

export LUCEE_CONCURRENT_MAP_IMPL=legacy

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