Migration - Upgrades

How to Convert Legacy CFML to Modern Components

Introduction

Legacy CFML apps often grew organically over years: large .cfm pages mixing SQL, Business logic, and HTML; global state; implicit scopes; tag-only Syntax; and tight coupling to ColdFusion Administrator settings. Moving to Modern components (CFCs), script Syntax, and a layered Architecture yields clear benefits: enhanced Security, testability, reuse, Performance, and Deployment flexibility (containers, CI/CD). It also positions your application for long-term support on Adobe ColdFusion (ACF) or Lucee, makes Maintenance easier, and reduces risk by encapsulating complexity within well-defined services.


Prerequisites / Before You Start

  • Backups

  • Versions and runtime targets

    • Decide runtime: Adobe ColdFusion (2021/2023) or Lucee 5.x/6.x.
    • Confirm compatibility of key Features (e.g., ORM/Hibernate, PDF, image manipulation).
    • Java version alignment (e.g., Java 11/17).
  • Dependencies and libraries

    • Tag libraries, Custom tags, UDFs, CFIDE components, third-party JARs, and mapping aliases.
    • Mail servers, datasources (DSNs), Redis/ElastiCache, S3, Elasticsearch, or any external services.
  • Tooling

    • CommandBox (for local servers, packages, CFConfig, Migrator/cfMigrations).
    • Git repository with a clean main branch; set up feature branches.
    • IDE with CFML support (VS Code + ColdBox Elixir/CommandBox extensions, or JetBrains/CFML plugins).
    • Linters: CFLint, TestBox for unit/Integration tests, qb/Quick if moving to ORM-like patterns.
  • Environment planning

    • Dev/stage/prod environment variables (.env) for secrets.
    • Docker or traditional server? Decide now—impacts Configuration strategy.
    • Routing/MVC framework selection (ColdBox or FW/1 are common) and DI container (WireBox or DI/1).
  • Stakeholders and scope

    • Document high-risk modules, Performance hotspots, and Compliance constraints (PII, PCI).
    • Define measurable outcomes: page response times, error budget, test coverage, and Deployment cadence.

Migration strategy Overview

  • Modern CFML concepts

    • Use CFCs with methods, accessors, and constructors.
    • Prefer cfscript over tag-based code for Business logic.
    • Isolate persistence (DAOs/Gateways), business rules (Services), and presentation (Views/Handlers).
    • Use dependency injection and centralized Configuration.
    • Externalize environment-specific settings.
  • Incremental vs. big-bang

    • Favor progressive Refactoring module-by-module with feature flags.
    • Wrap legacy pages behind new handlers as facades to reduce risk.
  • Choosing your stack

See also  How to Migrate ColdFusion Code to CFScript Syntax

Step-by-Step Migration guide

1) Inventory and Baseline

  • Run static analysis with CFLint; list global includes, cfapplication, custom tags.
  • Generate a route map of URLs to files.
  • Capture baseline metrics: average response times, error logs, and database Slow queries.

Simple mapping table to guide refactors:

Legacy pattern Modern replacement Example
.cfm mixing logic and view CFC service + view service.getOrders(); render view
cfinclude for shared logic CFC method orderService.calculateTotals()
Application.cfm Application.cfc onApplicationStart(), onRequestStart()
Implicit scopes Explicit var/local scopes var q = queryExecute(…)
Hard-coded DSN Env-based config server variables / .env

2) Set Up a Modern dev Environment

  • CommandBox

    • Start a Local server quickly:

      box server start cfengine=lucee@5

    • Pin the engine version in server.json.

  • CFConfig and environment variables

    • Store Server settings as JSON:

      box install commandbox-cfconfig commandbox-dotenv
      cfconfig export to=./config/cfconfig.json

    • .env example:

      CFCONFIG_adminPassword=superSecret
      DB_DSN=myAppDSN
      DB_USER=myuser
      DB_PASSWORD=${DB_PASSWORD_DEV}

  • Project structure

    • Create folders: /models, /handlers, /views, /tests, /config, /resources.

3) Extract Business Logic from CFMs

  • Identify shared logic in includes and convert to CFCs.

  • Example: legacy order totals in a .cfm include:
    cfml





  • Refactor into a component:
    cfml
    // /models/order/OrderService.cfc
    component accessors=true {
    public numeric function calcTotals(required array items) {
    var total = 0;
    for (var item in arguments.items) {
    total += item.price * item.qty;
    }
    return total;
    }
    }

  • Replace includes with service calls.


4) Convert Tag-Based Code to Script Syntax

  • Control flow:
    cfml
    // from


    // to
    if (condition) { … }

  • Queries:
    cfml
    // old


    SELECT * FROM users WHERE id =

    // modern
    q = queryExecute(
    “SELECT * FROM users WHERE id = ?”,
    [ { value: url.id, cfsqltype: “cf_sql_integer” } ],
    { datasource: application.settings.dsn }
    );

  • Components and properties:
    cfml
    component accessors=true {
    property name=”userGateway”;

    public User function getById(required numeric id) {
    return userGateway.findById(id);
    }
    }

Use var/local scoping consistently to avoid scope bleed.


5) Encapsulate Database access

  • Create Gateways/DAOs for each entity.

  • Enforce parameterization with cfqueryparam or queryExecute placeholders.

  • Transactions:
    cfml
    transaction {
    userGateway.insert(user);
    auditGateway.logInsert(user.id);
    }

  • Configure DSNs with environment variables, not hard-coded strings.


6) Introduce Dependency Injection

  • WireBox binder example (/config/WireBox.cfc):
    cfml
    component {
    function configure(){
    wirebox.map(“OrderService”).to(“models.order.OrderService”);
    wirebox.map(“UserGateway”).to(“models.user.UserGateway”);
    }
    }

  • Requesting dependencies:
    cfml
    component accessors=true {
    property name=”orderService” inject=”OrderService”;
    }

DI removes manual createObject() sprawl and centralizes wiring.


7) Decouple Presentation with MVC

  • Choose a framework:

    • ColdBox: handlers, views, layouts, interceptors.
    • FW/1: controllers, views, subsystems.
  • ColdBox route and handler sample:
    cfml
    // /config/Router.cfc
    function configure(){
    route( “orders/:id” ).to( “orders.show” );
    }

    // /handlers/orders.cfc
    component {
    property name=”orderService” inject=”OrderService”;

    function show(event, rc, prc){
    prc.order = orderService.get(rc.id);
    event.setView( “orders/show” );
    }
    }


8) Build RESTful Endpoints

  • ACF REST mapping:
    cfml
    // /rest/orders.cfc
    component rest=”true” restPath=”orders” {
    remote any function get(required numeric id) httpmethod=”GET” restPath=”{id}” produces=”application/json” {
    return serializeJSON( orderService.get(id) );
    }
    }

  • ColdBox REST handler is often simpler, with automatic JSON rendering.

  • Ensure consistent JSON serialization, date formats, and error envelopes.

See also  How to Ensure Backward Compatibility in ColdFusion

9) Migrate Sessions, Security, and Authentication

  • Replace cflogin/cflogout with service-driven auth.
  • Use bcrypt/argon2 for password hashing.
  • CSRF protection on form posts; synchronize tokens in forms.
  • Consider JWT or session cookies with secure/httponly/samesite flags.

10) Centralize Configuration

  • ColdBox config example (/config/ColdBox.cfc):
    cfml
    moduleSettings = {};
    environments = {
    development = function(){
    coldbox.debugMode = true;
    }
    };

  • CFConfig JSON snippet:
    json
    {
    “datasources”: {
    “MyDSN”: {
    “class”: “org.gjt.mm.mysql.Driver”,
    “connectionString”: “jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}”,
    “username”: “${DB_USER}”,
    “password”: “${DB_PASSWORD}”
    }
    }
    }

  • Load via CommandBox at server start.


11) Logging and Error handling

  • Use LogBox (ColdBox) with rolling files, JSON logs, and levels.
  • Global Error handler to normalize API errors:
    cfml
    try {
    // work
    } catch (any e) {
    log.error( “Unhandled error”, e );
    // render consistent error payload
    }

12) Automated Tests with TestBox

  • Unit test example:
    cfml
    // /tests/specs/OrderServiceSpec.cfc
    component extends=”testbox.system.BaseSpec” {
    function run(){
    describe( “OrderService”, function(){
    it( “calculates totals”, function(){
    var svc = new models.order.OrderService();
    var total = svc.calcTotals([ {price:10,qty:2},{price:5,qty:1} ]);
    expect( total ).toBe(25);
    });
    });
    }
    }

  • Add Integration tests for handlers and REST endpoints.


13) Database Migrations

  • Install cfmigrations:

    box install cfmigrations

  • Example migration:
    cfml
    component {
    function up( schema ){
    schema.create( “users”, function( table ){
    table.integer( “id” ).primaryKey();
    table.string( “email” ).unique();
    table.string( “passwordHash” );
    });
    }
    function down( schema ){
    schema.drop( “users” );
    }
    }


14) Containerization and Deployment

  • Dockerfile (Lucee example):
    dockerfile
    FROM ortussolutions/commandbox:latest
    COPY . /app
    WORKDIR /app
    ENV APP_ENV=production
    EXPOSE 8080
    CMD [“box”, “server”, “start”, “cfengine=lucee@5”, “host=0.0.0.0”]

  • CI/CD steps:

    • Run test suite.
    • Build image, run migrations, deploy.
    • Import CFConfig at container boot.

15) Optional: Adopt ORM or Query Builders

  • If moving from raw SQL to ORM (Hibernate), do it in phases.
  • Alternative: qb query builder for composable SQL with fewer footguns.

16) Performance and Profiling

  • Cache expensive service calls and query results.
  • Use template caching, ETags, and gzip/brotli at the web server.
  • Profile with FusionReactor or Lucee’s logs; review Slow queries and indexes.
  • Remove N+1 queries; batch loads; paginate results.

Risks, Common Issues, and How to Avoid Them

  • Hidden dependencies on Application.cfm or implicit scopes

    • Mitigation: Migrate to Application.cfc early; enable this.scopes to strict usage; add “use strict” lints.
  • Behavior differences between ACF and Lucee

    • Mitigation: Maintain a cross-engine test suite; avoid engine-specific quirks; pin versions.
  • Serialization surprises

    • Mitigation: Create explicit view models/DTOs; control date/timezones and null handling.
  • Query encoding and SQL injection

    • Mitigation: Always use cfqueryparam or parameter arrays; centralized datasource config.
  • Session-related regressions

    • Mitigation: Configure session cookies (secure, httponly, samesite) and test login flows under HTTP/HTTPS.
  • Over-Refactoring without tests

    • Mitigation: Establish a minimal TestBox suite before major moves; cover critical paths first.
  • Big-bang rewrites

    • Mitigation: Strangle pattern: route new features via new stack, proxy legacy endpoints, and retire pages gradually.
  • File path and mapping breakage

    • Mitigation: Replace relative file paths with mappings; declare them in Application.cfc or CFConfig.

Post-Migration Checklist

  • Application structure

    • Services, gateways/DAOs, handlers/controllers, and views separated.
    • No business logic left in .cfm view templates.
  • Configuration

    • All secrets and DSNs are externalized via environment variables.
    • CFConfig or framework config under Version control.
  • Security

    • Authentication, authorization, CSRF, and password hashing verified.
    • All data entry points parameterized.
  • Behavior parity

    • Critical features functionally match legacy app.
    • URLs and SEO-sensitive routes preserved or redirected.
  • Testing

    • Unit and integration tests passing in CI.
    • Smoke tests executed in staging.
  • Observability

    • Logging levels appropriate; errors actionable.
    • Metrics and alerts configured (uptime, error rate, response time).
  • Performance

    • Target response times achieved; database slow queries addressed.
    • Caching configured where appropriate.
  • Deployment

    • Repeatable build with CommandBox/Docker.
    • Database migrations applied automatically, with rollback plan.
  • Documentation

    • README updated with setup, scripts, and operational runbooks.
See also  How to Test Performance Before and After Migration

Practical Examples and Snippets

Application.cfc baseline

cfml
component {
this.name = “MyModernApp”;
this.sessionManagement = true;
this.sessionTimeout = createTimeSpan(0,0,30,0);

function onApplicationStart(){
application.settings = {
dsn = server.system.environment.DB_DSN
};
}

function onRequestStart( required string targetPage ){
// Security headers, logging correlation IDs, etc.
}
}

WireBox + Service usage in a handler

cfml
component {
property name=”userService” inject=”UserService”;

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

Safe file uploads

cfml
if ( structKeyExists( form, “upload” ) ) {
var result = fileUpload(
expandPath( “/resources/uploads” ),
“upload”,
“image/png,image/jpeg”,
“makeUnique”
);
}


Configuration and Commands Cheatsheet

  • Start server

    box server start cfengine=adobe@2023

  • Install ColdBox and TestBox

    box install coldbox testbox

  • Export/import CF settings

    cfconfig export to=./config/cfconfig.json
    cfconfig import from=./config/cfconfig.json

  • Run tests

    box testbox run


Mapping Legacy to Modern: Quick reference

Area Legacy Modern
App bootstrap Application.cfm Application.cfc
Business logic cfinclude, UDFs in pages CFCs (services), DI container
DB access cfquery scattered Gateways/DAOs, queryExecute, transactions
Presentation Mixed HTML/CFML MVC views + handlers
Config Admin console/manual CFConfig + env vars/.env
Auth cflogin Auth service, JWT/session, secure cookies
Testing Manual clicking TestBox unit/integration
Deployment Manual file copy CommandBox, Docker, CI/CD
Monitoring ad-hoc logs LogBox, FusionReactor, centralized logging

FAQ

What’s the fastest way to start modernizing without breaking everything?

Begin with the strangler approach: introduce an MVC framework, route a small feature through a new handler and service, and proxy everything else to legacy pages. Extract logic into CFCs incrementally. Put tests around critical functions before moving them.

Should I choose Adobe ColdFusion or Lucee for Modernization?

Both are viable. Choose ACF if you rely on enterprise features and official support; pick Lucee for open-source, container-first workflows and performance. Maintain an engine-agnostic code style and verify with cross-engine tests when possible.

Do I need to switch to ORM to be “modern”?

No. Modernization is about modularity, testability, and clean Architecture. You can keep queryExecute/sql if you encapsulate persistence and use parameterization. ORM or a query builder like qb can be adopted gradually.

How do I manage configuration across environments?

Externalize it. Use CFConfig to version-control server settings and .env for secrets. Read environment variables in Application.cfc or framework config. Avoid hard-coded DSNs, paths, and credentials.

What testing coverage is enough to proceed?

Aim for tests on high-risk modules: authentication, payment flows, and key reports first. A practical starting point is unit tests for core services plus a few integration tests for main routes. Expand coverage as you refactor more modules.

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.