Migration - Upgrades

How to Switch from ColdFusion to Modern CFML Frameworks

Why moving from classic ColdFusion to Modern CFML frameworks pays off

CFML has evolved beyond monolithic cfinclude chains and page-level logic. Modern frameworks like ColdBox and FW/1, combined with tools such as CommandBox, TestBox, WireBox, qb/Quick, and LogBox, give you structure, testability, Performance, and portability (including running on Lucee). Migrating unlocks benefits such as proper MVC organization, dependency injection, routing, modularity, automated testing, environment-based Configuration, and cloud-friendly Deployment (Docker/Kubernetes). The goal isn’t to abandon CFML—it’s to adopt a maintainable, future-proof way of building CFML applications.


Prerequisites / Before You Start

  • Inventory and Audit
    • Source code repository set up (Git or similar).
    • List of datasources (DSNs), file storage locations, Scheduled tasks, CF Admin settings.
    • Third-party dependencies (taglibs, custom Java JARs, CFIDE usage, PDF/Report generation, mail, S3, Redis, etc.).
  • Backups and snapshots
  • Target runtime and versions
    • Choose runtime: Adobe ColdFusion (2018/2021/2023) or Lucee (5.4+/6.x).
    • Confirm compatible Java version for your target runtime.
    • Select framework: ColdBox (recommended for feature-complete MVC) or FW/1 (lightweight MVC).
  • Development tooling
    • Install CommandBox (box CLI).
    • Install Java JDK compatible with chosen engine.
    • IDE/editor with CFML support (VS Code + CFML extension).
  • Environments
    • Local development environment using CommandBox.
    • Staging/UAT mirroring production as closely as possible.
    • Production strategy (VMs or Docker).
  • Documentation
    • Coding Standards: prefer CFScript, MVC boundaries, naming conventions.
    • Branching and release process.
  • Security baseline
    • Password hashing policy (bcrypt/Argon2).
    • TLS/HTTPS and HSTS plans.
    • Least-privilege database credentials.
  • Performance baseline
    • Capture current response times, throughput, and error rates to compare after Migration.

Step-by-Step Migration guide

1) Pick your runtime and CFML framework

  • Runtime:
    • Lucee offers speed, container friendliness, and open-source flexibility.
    • Adobe ColdFusion offers enterprise Features and official support.
  • Framework:
    • ColdBox: opinionated, batteries included (routing, DI via WireBox, logging via LogBox, testing via TestBox, ORM, modules).
    • FW/1: minimalist MVC, easy to adopt if you want a lighter touch.
  • Data access:
    • qb (query builder) and Quick (ActiveRecord-style ORM) offer safer, cleaner Data access.
  • Testing:

Tip: You can mix old and new during an incremental migration. Choose ColdBox if you want a full ecosystem and conventions; choose FW/1 for a gentler, incremental approach.

See also  How to Upgrade ColdFusion License After Migration

2) Stand up a Modern dev environment with CommandBox

  • Install CommandBox and start your app in a sandboxed server:

box install
box server start

  • Create a basic server.json:
    json
    {
    “app”: { “cfengine”: “lucee@5.4” },
    “web”: { “http”: { “port”: 8080 } },
    “openBrowser”: true,
    “JVM”: { “heapSize”: “512” }
    }

  • Switch engines easily:

box server stop
box server start cfengine=adobe@2021

  • Initialize a ColdBox app:

box install coldbox
box coldbox create app name=MyApp skeleton=AdvancedScript

For FW/1:

box install fw1
box fw1 create app name=MyApp

Use CommandBox to manage dependencies via box.json and to run scripts, tests, and DB migrations.


3) Convert Application.cfc and bootstrap your framework

Move from scattered Application.cfm or tag-based Application.cfc to a script-based Application.cfc that initializes your framework.

  • Example (ColdBox + Lucee):
    cfc
    component {
    this.name = “MyApp”;
    this.applicationTimeout = createTimeSpan(1,0,0,0);
    this.sessionManagement = true;
    this.sessionTimeout = createTimeSpan(0,0,30,0);
    this.mappings[“/root”] = expandPath(“/”);

    function onApplicationStart(){
    application.cbBootstrap = new coldbox.system.Bootstrap( expandPath(“/”) );
    application.cbBootstrap.loadColdbox();
    return true;
    }

    function onRequestStart( targetPage ){
    application.cbBootstrap.onRequestStart( arguments.targetPage );
    }

    function onError( exception, eventName ){
    // Centralized Error handling, LogBox Integration recommended
    }
    }

  • For FW/1:
    cfc
    component extends=”framework.one” {
    this.name = “MyApp”;
    variables.framework = { home=”main.default”, base=”/”, debug=false };

    function setupApplication(){
    // Wire services, set mappings, load .env
    }
    }

Use environment variables for environment-specific settings (DB credentials, API keys). With CommandBox dotenv:

box install commandbox-dotenv

Add a .env file and load it at startup.


4) Carve out MVC: routes, handlers/controllers, views, and services

  • Replace cfinclude chains with views/layouts and controllers/handlers.

  • ColdBox routing example (config/Router.cfc):
    cfc
    component extends=”coldbox.system.web.routing.Router” {
    function configure(){
    route( “/”,”main.index” );
    route( “/users”, “users.index” ).methods(“GET”);
    route( “/users/:id”, “users.show” ).methods(“GET”);
    }
    }

  • ColdBox handler example (handlers/Users.cfc):
    cfc
    component {
    property name=”userService” inject=”UserService”;

    function index( event, rc, prc ){
    prc.users = userService.list();
    event.setView( “users/index” );
    }

    function show( event, rc, prc ){
    prc.user = userService.get( rc.id );
    event.setView( “users/show” );
    }
    }

  • FW/1 controller (controllers/users.cfc):
    cfc
    component {
    property name=”userService” inject;

    function default( rc ){
    rc.users = userService.list();
    }

    function show( rc ){
    rc.user = userService.get( rc.id );
    }
    }

  • Views become CFM templates in views/users/index.cfm and views/users/show.cfm.

Keep Business logic in services (model layer). Limit controllers to orchestration.


5) Add Dependency Injection with WireBox

Stop manually creating components. Configure DI and let the framework inject them.

  • WireBox mapping (config/WireBox.cfc):
    cfc
    component {
    function configure(){
    map( “UserService” ).to( “models/UserService” );
    map( “UserGateway” ).to( “models/gateways/UserGateway” );
    }
    }

  • Inject and use:
    cfc
    component {
    property name=”userGateway” inject=”UserGateway”;

    function list(){
    return userGateway.getAll();
    }
    }

Injection reduces coupling and makes testing easier.


6) Modernize data access: qb/Quick, SQL safety, and migrations

  • Parameterized queries remain important; prefer qb for readability and safety.
    cfc
    component {
    property name=”qb” inject=”provider:QueryBuilder@qb”;

    function findByEmail( email ){
    return qb.table(“users”)
    .where( “email”, email )
    .first();
    }
    }

  • Quick (ORM) example:
    cfc
    component extends=”quick.models.BaseEntity” table=”users” accessors=”true” {
    property name=”id”;
    property name=”email”;
    property name=”name”;

    function posts(){
    return hasMany( “Post”, “user_id” );
    }
    }

  • Database migrations (cfmigrations):

box install cfmigrations
box migrate init
box migrate create add_users_table
box migrate up

Migrations ensure reproducible schema changes across environments.


7) Security, auth, and middleware

  • Use framework-level security:

    • ColdBox Security module for auth/roles.
    • Interceptors or middleware to enforce HTTPS, Rate limiting, and CSRF tokens.
  • Passwords:
    cfc
    var hash = bcryptHash( password, rounds=12 );
    var ok = bcryptCheck( password, hash );

  • Input validation and encoding:

    • Always use cfqueryparam or qb parameterization.
    • Encode output to prevent XSS.
  • Secrets from environment variables:

DB_PASSWORD=${DB_PASSWORD}
JWT_SECRET=${JWT_SECRET}

Add CORS, CSP, and secure headers centrally via interceptors/middleware.


8) Logging, Error handling, and monitoring

  • Configure LogBox (ColdBox):
    cfc
    logBox = {
    appenders = { console = { class=”coldbox.system.logging.appenders.ConsoleAppender” } },
    root = { level=”INFO”, appenders=”console” }
    };

  • Centralized error page + structured logs. Consider external sinks (Sentry, Graylog, ELK).

  • Add request IDs in MDC to correlate logs.

See also  How to Migrate from ColdFusion to Lucee (Open Source)

9) Performance and caching

  • CacheBox for view/data caching and Rate limiting.
  • Use query caching selectively via qb or native CFML query caching.
  • Profile with FusionReactor or Lucee Profiler. Fix N+1 queries in Quick/ORM.

10) Testing and CI/CD

  • TestBox example:
    cfc
    component extends=”testbox.system.BaseSpec” {
    function run(){
    describe( “UserService”, function(){
    it( “lists users”, function(){
    var svc = getInstance( “UserService” );
    var users = svc.list();
    expect( users ).toBeArray();
    });
    });
    }
    }

  • Run tests:

box testbox run

  • CI pipeline steps:
    • box install
    • box testbox run
    • box migrate up
    • bundle and deploy (or build Docker image)

11) Incremental Migration strategy (strangler fig)

  • Proxy strategy:
    • Route new URLs to framework handlers.
    • Legacy pages remain served while you refactor them feature-by-feature.
  • Adapter layer:
    • Create service wrappers that old code and new code can share.
  • Rewrite rules:
    • Use CommandBox or web server rewrites to map old endpoints to new routes.
  • Acceptance tests:
    • Cover legacy behavior before refactor; validate parity after.

12) Deployment to staging and production

  • Docker example (CommandBox-based):
    dockerfile
    FROM ortussolutions/commandbox:adobe2021
    WORKDIR /app
    COPY . .
    RUN box install
    EXPOSE 8080
    CMD [ “box”, “server”, “start”, “cfengine=adobe@2021”, “console” ]

  • Environment config:

    • Use container env vars for DSNs, mail server, API keys.
  • Health checks:

  • Run migrations automatically on startup or as a separate job.


Risks, Common Issues, and How to Avoid Them

  • ACF vs Lucee differences
    • Null handling varies; test for null/empty explicitly.
    • Built-in functions and member functions may differ slightly; run automated tests under both engines if you plan to support both.
    • PDF/image tags and CFHTMLtoPDF behaviors can diverge; consider 3rd-party libraries if necessary.
  • Scoping and lifecycle
    • Ensure no reliance on implicit scope assignments. Use var/local scoping in functions.
    • Session and application state must be managed via framework or explicit APIs.
  • Encoding and locale
    • Standardize on UTF-8. Set this.charset in Application.cfc and DB connections.
    • Be careful with date/timezones; prefer UTC in persistence.
  • Query behavior
    • Don’t rely on column order; reference by name.
    • Always parameterize inputs; never string-concatenate SQL.
  • Long-running requests
    • Move batch work to scheduled tasks or asynchronous queues instead of blocking HTTP requests.
  • File paths and mappings
    • Use expandPath() or framework mappings. Avoid absolute paths tied to legacy servers.
  • Security regressions
    • Revisit Authentication/authorization. Replace home-grown MD5/SHA-1 hashing with bcrypt.
    • Add CSRF tokens for unsafe HTTP methods.
  • Over-big-bang rewrites
    • Prefer iterative migration with feature toggles and strangler pattern to reduce risk.
  • Hidden dependencies
    • Watch for CFIDE or undocumented server-level mappings; replicate or remove them.

Proactive testing, clear Coding Standards, and staged rollouts reduce the chance of surprises.


Post-Migration Checklist / Validation Steps

  • Functional parity
    • Business-critical flows (login, checkout, reporting) pass manual and automated tests.
    • File uploads/downloads, e-mail sending, and scheduled tasks work as expected.
  • Performance baseline
    • Compare p50/p95 latency and throughput to Pre-migration metrics.
    • Check DB query counts and add indexes if needed.
  • Security and Compliance
    • All endpoints behind HTTPS; HSTS enabled.
    • Password hashing verified; session cookies secure/HTTPOnly; CSRF protection enabled.
    • Static code analysis and dependency scans clean.
  • Observability
    • Application logs contain request IDs, user IDs (when authenticated), and error stack traces.
    • Alerts configured for error rate spikes and slowdowns.
  • Configuration correctness
    • Environment variables present in all environments.
    • DSNs validated; mail server tested; external APIs reachable.
  • SEO and URL behavior
    • Old URLs either still function or return 301 to new routes.
    • Robots.txt and sitemap.xml served correctly.
  • Data integrity
    • Migrations applied; data validated for key entities and relationships.
  • Documentation
    • Readme updated with startup instructions, scripts, and Troubleshooting.
    • Runbooks available for deployments and rollbacks.
See also  How to Roll Back a Failed ColdFusion Upgrade

Legacy-to-Modern feature mapping (Quick reference)

  • Application.cfm/Application.cfc bootstrap → Framework bootstrap (ColdBox/FW/1)
  • cfinclude for composition → Views/layouts/partials
  • Page-level logic → Controllers/handlers + service layer
  • Manually new components → DI with WireBox
  • Raw cfquery → qb/Quick, parameterized queries
  • ad hoc error handling → Centralized Error handler + LogBox
  • Manual routing → Router.cfc (ColdBox) or FW/1 implicit routing
  • Ad hoc tests → TestBox specs and suites
  • Manual deployment → CommandBox scripts, Docker, CI/CD

Example project skeleton (ColdBox)

  • Directory structure:

/app
/handlers
Main.cfc
Users.cfc
/models
/gateways
/services
/views
/users
index.cfm
show.cfm
/config
Coldbox.cfc
Router.cfc
WireBox.cfc
Application.cfc
box.json

  • box.json (dependencies and scripts):
    json
    {
    “name”: “myapp”,
    “dependencies”: {
    “coldbox”: “^7.0.0”,
    “qb”: “^9.0.0”,
    “quick”: “^8.0.0”,
    “testbox”: “^4.5.0”
    },
    “scripts”: {
    “start”: “server start”,
    “test”: “testbox run”,
    “migrateUp”: “migrate up”
    }
    }

  • Sample ColdBox configuration (config/Coldbox.cfc snippet):
    cfc
    component {
    function configure(){
    coldbox = {
    defaultLayout = “Main.cfm”,
    defaultEvent = “main.index”,
    debugMode = false
    };
    environments = {
    production = function(){
    coldbox.debugMode = false;
    }
    };
    }
    }

This skeleton gives you MVC boundaries, routing, DI, logging, and testing harnesses out of the gate.


Practical example: converting a legacy page to a routed action

  • Legacy index.cfm:
    cfm



    SELECT id, name, email FROM users ORDER BY name


    #name# – #email#

  • Modernized ColdBox:

    • models/services/UserService.cfc:
      cfc
      component {
      property name=”qb” inject=”provider:QueryBuilder@qb”;
      function list(){
      return qb.table( “users” ).orderBy( “name” ).get();
      }
      }

    • handlers/Users.cfc:
      cfc
      component {
      property name=”userService” inject=”UserService”;
      function index( event, rc, prc ){
      prc.users = userService.list();
      event.setView( “users/index” );
      }
      }

    • views/users/index.cfm:
      cfm

      Users

      • #encodeForHtml(u.name)# – #encodeForHtml(u.email)#

    • Router.cfc: route(“/”, “users.index”)

This yields a safer, testable, and routable endpoint.


Migration milestones and suggested timeline

  • Week 1–2: Setup CommandBox, choose framework, convert Application.cfc, stub routing, add DI.
  • Week 3–4: Migrate critical flows into MVC, adopt qb/Quick, introduce TestBox.
  • Week 5–6: Wire security middleware, logging and error handling; implement CI/CD and migrations.
  • Week 7+: Incremental refactors, Performance tuning, decommission legacy routes.

Adjust based on team size, codebase complexity, and regulatory constraints.


FAQ

Do I need to rewrite all CFML tags into script?

No. While adopting CFScript improves readability and testing, CFML tags can coexist with script. Prioritize moving Business logic into CFCs and services. Convert high-churn areas first; views can remain taggy if they are simple.

Should I choose Lucee or Adobe ColdFusion?

Both are viable. Lucee is open-source, fast, and container-friendly. Adobe ColdFusion offers enterprise Features, bundled tooling, and commercial support. If you rely heavily on Adobe-only features (e.g., specific PDF integrations), Adobe may be safer. If you want flexibility and lower Licensing costs, Lucee is attractive. Test your app on both.

Can I run Legacy code inside a modern framework during migration?

Yes. Use the strangler pattern. Route new endpoints through the framework while keeping legacy pages accessible. You can forward requests or proxy old URLs, then refactor page-by-page into handlers and services.

How do I handle ORM if I previously used Hibernate ORM?

You can keep ACF Hibernate temporarily while migrating business logic into services, or move to Quick ORM for a simpler, CFML-native approach. For complex domain models, consider a repository pattern with qb and introduce Quick incrementally.

How long does a typical migration take?

It varies widely. Small apps may transition in weeks; large monoliths can take months. The biggest determinant is test coverage and the degree of entanglement between UI and business logic. Plan for iterative releases and measure parity with automated tests.

About the author

Aaron Longnion

Aaron Longnion

Hey there! I'm Aaron Longnion — an Internet technologist, web software engineer, and ColdFusion expert with more than 24 years of experience. Over the years, I've had the privilege of working with some of the most exciting and fast-growing companies out there, including lynda.com, HomeAway, landsofamerica.com (CoStar Group), and Adobe.com.

I'm a full-stack developer at heart, but what really drives me is designing and building internet architectures that are highly scalable, cost-effective, and fault-tolerant — solutions built to handle rapid growth and stay ahead of the curve.