Lucee 5 to 6 Migration Guide
Lucee 5 to 6 Migration Guide
Lucee 6 introduces several breaking changes that may affect existing applications. This guide provides a systematic approach to identify and resolve compatibility issues during migration from Lucee 5 to Lucee 6.
Migration Strategy Overview
The recommended approach for migrating to Lucee 6 involves:
- Enable compatibility mode: Use environment variables to maintain backward compatibility temporarily
- Enable warning logs: Set logging to capture deprecated functionality usage
- Identify issues: Run the application to collect warning logs
- Fix systematically: Address each identified issue by updating code
- Verify changes: Test application without compatibility settings
- Remove compatibility flags: Disable environment variables once all issues are resolved
This systematic approach allows efficient identification and resolution of all compatibility issues through log analysis rather than manual code inspection.
Breaking Changes and Solutions
1. DeserializeJSON Empty String Handling
Issue: Lucee 6's DeserializeJSON()
function no longer accepts empty strings as valid input. In Lucee 5, DeserializeJSON("")
would return an empty string, but in Lucee 6 this throws an exception: "input value cannot be empty string."
Temporary Solution
Set the environment variable to enable backward compatibility:
LUCEE_DESERIALIZEJSON_ALLOWEMPTY=true
Identifying Issues
- Set application logging to WARN level
- Run your application to generate warning logs
- Look for log entries like:
Deprecated functionality used at [...]. An empty string was passed as a value to the function DeserializeJSON.;
Permanent Fix
Update code to handle empty strings before calling DeserializeJSON()
:
Before (Lucee 5):
<cfset result = DeserializeJSON(someVariable)>
After (Lucee 6):
<cfset result = len(trim(someVariable)) ? DeserializeJSON(someVariable) : "">
Verification Steps
- Update all identified instances
- Test with appropriate scenarios
- Verify no warning logs are generated
- Set
LUCEE_DESERIALIZEJSON_ALLOWEMPTY=false
and confirm application functions correctly
2. Query Parameter Empty String to NULL Conversion
Issue: The <cfqueryparam>
tag and params
attribute in <cfquery>
no longer convert empty strings to NULL values for various SQL types. In Lucee 5, empty strings were implicitly converted to NULL for many data types.
Affected SQL Types
- BIGINT, BIT
- BLOB, CLOB
- DECIMAL, NUMERIC
- DOUBLE, FLOAT
- BINARY types (VARBINARY, LONGVARBINARY, BINARY)
- REAL
- TINYINT, SMALLINT, INTEGER
- DATE, TIME, TIMESTAMP
Temporary Solution
Set the environment variable to enable backward compatibility:
LUCEE_QUERY_ALLOWEMPTYASNULL=true
Identifying Issues
- Set datasource logging to WARN level
- Run your application to generate warning logs
- Look for log entries like:
Deprecated functionality used at [...]. An empty string was passed as a value for type [TYPE]. Currently, this is treated as null, but it will be rejected in future releases.
Permanent Fix
Explicitly handle empty strings in query parameters:
Before (Lucee 5):
<cfquery name="myQuery" datasource="myDS">
SELECT * FROM users
WHERE id = <cfqueryparam value="#userID#" cfsqltype="cf_sql_integer">
</cfquery>
After (Lucee 6):
<cfquery name="myQuery" datasource="myDS">
SELECT * FROM users
WHERE id = <cfqueryparam value="#len(trim(userID)) ? userID : javaCast('null', '')#" cfsqltype="cf_sql_integer" null="#not len(trim(userID))#">
</cfquery>
For dates:
<cfquery name="myQuery" datasource="myDS">
SELECT * FROM events
WHERE event_date = <cfqueryparam value="#isDate(eventDate) ? eventDate : javaCast('null', '')#" cfsqltype="cf_sql_timestamp" null="#not isDate(eventDate)#">
</cfquery>
Verification Steps
- Categorize issues by SQL type
- Update all identified query parameters
- Test all modified queries
- Verify no warning logs are generated
- Set
LUCEE_QUERY_ALLOWEMPTYASNULL=false
and confirm application functions correctly
3. Precise Math Performance Impact
Issue: Lucee 6 switched from using double
as the default numeric type to using BigDecimal
. While BigDecimal provides higher precision, it introduces significant performance overhead for mathematical calculations.
Temporary Solution
Set the environment variable to maintain double-based calculations globally:
LUCEE_PRECISE_MATH=false
Identifying Performance Issues
- Perform profiling to identify performance-critical code paths
- Analyze areas with heavy mathematical calculations
- Benchmark performance with and without precise math
- Focus on loops, financial calculations, and data processing operations
Verification Steps
- Document all locations where precise math is disabled and rationale
- Establish performance benchmarks
- Verify precision-critical calculations maintain accuracy
- Test application without global
LUCEE_PRECISE_MATH
setting - Run performance tests under various scenarios
4. DateTimeFormat Mask Padding Changes
Issue: The datetimeFormat()
function's WW
and FF
masks no longer return zero-padded strings.
In Lucee 5, these masks would return 2-digit strings (e.g., "04", "00"), but Lucee 6 returns single digits (e.g., "4", "0") due to the switch from SimpleDateFormat
to DateTimeFormatter
.
Affected Masks
- WW: Week of month (returns "4" instead of "04")
- FF: Fraction of second (returns "0" instead of "00")
Root Cause
SimpleDateFormat
interpreted WW
as a two-character pattern requiring zero-padding, while DateTimeFormatter
treats WW
as two separate W
patterns without automatic padding.
Temporary Solution
Set the environment variable to enable legacy formatting behavior:
LUCEE_DATETIMEFORMAT_MODE=classic
Added in Lucee 6.2.2.53
Identifying Issues
- Search codebase for
datetimeFormat()
calls usingWW
orFF
masks - Test date formatting outputs to identify padding discrepancies
- Look for code that depends on specific string lengths from date formatting
Permanent Fix
Update code to handle formatting differences or use alternative approaches:
Before (Lucee 5):
<cfset weekOfMonth = datetimeFormat('2018-01-25 10:00:00', 'WW')>
<!-- Returns "04" -->
After (Lucee 6) - Option 1: Use classic mode temporarily:
<!-- Set LUCEE_DATETIMEFORMAT_MODE=classic -->
<cfset weekOfMonth = datetimeFormat('2018-01-25 10:00:00', 'WW')>
<!-- Returns "04" with classic mode -->
After (Lucee 6) - Option 2: Manual padding:
<cfset weekOfMonth = numberFormat(datetimeFormat('2018-01-25 10:00:00', 'W'), '00')>
<!-- Returns "04" -->
After (Lucee 6) - Option 3: String padding:
<cfset weekOfMonth = right('0' & datetimeFormat('2018-01-25 10:00:00', 'W'), 2)>
<!-- Returns "04" -->
Verification Steps
- Identify all uses of
WW
andFF
masks in date formatting - Update code to handle padding requirements
- Test all date formatting scenarios
- Verify string length dependencies are maintained
- Set
LUCEE_DATETIMEFORMAT_MODE=modern
and confirm application functions correctly
Environment Variables Reference
Variable | Purpose | Default | Migration Phase |
---|---|---|---|
LUCEE_DESERIALIZEJSON_ALLOWEMPTY |
Allow empty strings in DeserializeJSON | false | Temporary compatibility |
LUCEE_QUERY_ALLOWEMPTYASNULL |
Convert empty strings to NULL in queries | false | Temporary compatibility |
LUCEE_PRECISE_MATH |
Use BigDecimal for all calculations | true | Performance optimization |
LUCEE_DATETIMEFORMAT_MODE |
Date formatting behavior (classic /modern ) |
modern | Temporary compatibility |
Logging Configuration
Set appropriate logging levels to capture compatibility warnings:
Testing Strategy
Unit Tests
Create tests for edge cases introduced by breaking changes:
<cfcomponent extends="TestCase">
<cffunction name="testDeserializeJSONEmpty">
<cfset var result = "">
<cfset var emptyString = "">
<!-- Test empty string handling -->
<cfset result = len(trim(emptyString)) ? DeserializeJSON(emptyString) : "">
<cfset assertEquals("", result)>
</cffunction>
<cffunction name="testQueryParamNull">
<cfset var userID = "">
<!-- Test NULL parameter handling -->
<cfquery name="testQuery" datasource="test">
SELECT COUNT(*) as cnt FROM users
WHERE id = <cfqueryparam value="#len(trim(userID)) ? userID : javaCast('null', '')#"
cfsqltype="cf_sql_integer"
null="#not len(trim(userID))#">
</cfquery>
<cfset assertTrue(isDefined("testQuery.cnt"))>
</cffunction>
<cffunction name="testDateTimeFormatPadding">
<cfset var testDate = '2018-01-25 10:00:00'>
<cfset var weekOfMonth = "">
<!-- Test WW mask padding -->
<cfset weekOfMonth = numberFormat(datetimeFormat(testDate, 'W'), '00')>
<cfset assertEquals("04", weekOfMonth)>
<cfset assertEquals(2, len(weekOfMonth))>
</cffunction>
</cfcomponent>