Upgrading
Upgrading from 4.0 to 4.1
Within the 4.x line there are no breaking framework changes — upgrading from any 4.0.x to 4.1.x is the same vendor/wheels/ swap as a patch upgrade (see Manual upgrades). The work that the swap does not do for you is a short list of edits to the app-owned public/Application.cfc: that file is created once from the template and is yours, so framework updates never reach it. This page collects every one of those edits in one place, with the version it landed in and whether skipping it fails safe.
Swap the framework
Section titled “Swap the framework”-
Back up the current framework. Move — don’t delete —
vendor/wheels/aside (e.g.vendor/wheels-4.0.x.bak/, outside version control). Moving it back is the whole rollback. -
Install 4.1.x. Extract the 4.1
vendor/wheels/in its place (orgit checkoutthe tag if you vendor the framework). -
Reload and run your suite. Use
?reload=true&password=…and/wheels/app/tests. Then apply thepublic/Application.cfcedits below.
The public/Application.cfc edits
Section titled “The public/Application.cfc edits”Each edit is app-owned, so it is yours to apply. “Fails safe if skipped” means the app keeps working correctly without it; where it does not, the consequence is stated.
Session cookie Secure flag (since 4.0.4; corrected 4.1.2)
Section titled “Session cookie Secure flag (since 4.0.4; corrected 4.1.2)”The this.sessionCookie block (httpOnly, sameSite, secure) has been in the template since 4.0.4 (#2927). 4.1.2 then made the secure flag follow the actual request scheme, not just WHEELS_ENV=production (#3830).
- Apps created with 4.0.4–4.1.1 have the block and should update
this.sessionCookie.secureto the form in the CHANGELOG #3830 entry (or setthis.sessionCookie.secure = true;inconfig/app.cfmwhen the site is HTTPS-only). - Apps created with 4.0.0–4.0.3 have no
this.sessionCookieblock at all and should add the whole block from the template.
Does not fail safe: an app switched to production through config/environment.cfm can issue its session cookie without Secure over HTTPS until this is applied.
DI container guard in onError (since 4.0.4)
Section titled “DI container guard in onError (since 4.0.4)”onError now only rebuilds the DI container when it never came up, instead of on every error page (#3061). Added to the template in 4.0.4, so apps created on 4.0.0-4.0.3 are missing it. Without the guard, an error page can replace the live container and wipe your config/services.cfm registrations and cached singletons.
Does not fail safe: adopt it.
Adobe teardown guards in onError / onSessionEnd (staged 4.0.4-4.1.0)
Section titled “Adobe teardown guards in onError / onSessionEnd (staged 4.0.4-4.1.0)”The handlers are guarded against a torn-down application scope during applicationStop() and session reaping (#3379). These guards were staged into the template across 4.0.4 to 4.1.0, so a copy created on an older 4.0.x may have only part of them — diff the whole onError / onSessionEnd against the 4.1 template.
Fails safe on Lucee and BoxLang; does not on Adobe CF 2023/2025, where an unguarded handler can turn a reload or idle-timeout teardown into an HTTP 500 for the whole site. See the Aside in Manual upgrades.
jBCrypt load path (4.1.0)
Section titled “jBCrypt load path (4.1.0)”A block puts the bundled jBCrypt jar on the application’s Java load paths (#3571) so bcryptHash() / bcryptVerify() run in native Java. The exact block is in the Aside in Manual upgrades.
Does not fail safe for auth apps: without it, bcryptHash() at the default cost can exceed the request timeout on Lucee 7. Any app that logs in with bcryptVerify() (as wheels generate auth sets up) is affected.
.env boolean parser (4.1.1)
Section titled “.env boolean parser (4.1.1)”The .env parser stops coercing a numeric 1 (such as 1.0) to the boolean true (#3641). The before/after is in the Aside in Manual upgrades.
Fails safe unless you store a boolean-looking numeric in .env and read it as a flag.
Isolated test-application include (since 4.0.6)
Section titled “Isolated test-application include (since 4.0.6)”The template includes vendor/wheels/events/testcontext.cfm after config/app.cfm so both the app and core test runners bind a separate CFML application scope instead of the live one (#3374). Added to the template in 4.0.6, so apps created on 4.0.0-4.0.5 are missing it.
Fails safe for production; without it, the test suites run against your live application scope rather than an isolated one. Adopt the include if you run the framework or app suite against your app.
Reload-password handoff
Section titled “Reload-password handoff”The ?reload=true gate itself is framework code (in onapplicationstart) and has failed closed since 4.0.4 — it does not live in your public/Application.cfc, so there is no risk of an app leaving the reload endpoint open. What public/Application.cfc carries is only the reload-password handoff that passes the supplied password through to the framework gate.
Fails safe: the gate rejects an unauthorised reload regardless. If your copy predates the current template, diff the password handoff so it matches; wheels upgrade check flags the drift.
Migrations: empty-string defaults (4.1.0)
Section titled “Migrations: empty-string defaults (4.1.0)”Since 4.1.0 the migrator rejects default='' on string, text and char columns (#3419). The migration fails with Wheels.InvalidDefault: “An empty string default is not allowed for string columns.” Before 4.1.0 most adapters dropped the DEFAULT clause without a word, and PostgreSQL emitted DEFAULT '', so the same migration built different schemas on different databases.
Does not fail safe on a fresh database. Migrations you have already applied are unaffected. The failure shows up when a migration runs from zero: a new environment, CI, a teammate’s first wheels migrate latest. Check your migrations for default='' on those column types and, for each one:
- remove the option (
t.string(columnNames='title', allowNull=true)): this builds the column most adapters built before 4.1.0; - or pass a real value (
default='draft'); - or use
default='NULL'for an explicitDEFAULT NULL.
default='' is still accepted on boolean, date, datetime, time, timestamp, decimal, float and integer columns, where it produces DEFAULT NULL.
Verify
Section titled “Verify”- Reload (
?reload=true&password=…) and confirm the app starts clean — checkwheels.logfor warnings. - Run your suite (
/wheels/app/tests). - If you use authentication, time a login —
bcryptVerify()should be fast (native Java), not seconds. - Over HTTPS, confirm the session cookie carries
Secure. - Run
wheels migrate latestagainst an empty database (a scratch environment or CI) to catch anydefault=''on a string column.
Related guides
Section titled “Related guides”- Manual upgrades — the swap mechanics and the exact
public/Application.cfccode blocks. - Upgrading from 3.x to 4.0 — the breaking-change map for 3.x apps.
- Changelog — the full per-version entry list.