Contact Us
Technical deep-dives October 31  •  16 min read

ColdFusion migration: CFML internals, Lucee and a practical path to Java

Adobe ColdFusion 2021 extended support ends November 10, 2026. Here is how CFML scopes, Application.cfc, cfquery and Evaluate map to Spring Boot, with real before/after code.
← All posts

A ColdFusion migration usually starts with a date on Adobe's support calendar and ends up being a question about code. Should the estate move to a newer Adobe release, to the open-source Lucee engine, or off CFML entirely and onto Java? The answer depends less on licensing than on what your templates actually do: how much they rely on unscoped variables, Evaluate(), query-of-queries, cfinclude chains and built-ins such as cfdocument.

This guide is for CTOs, architects and the senior CFML developers who will do the work. It covers the support status of Adobe ColdFusion and Lucee, the CFML internals that make conversion harder than it looks, a decision table for the realistic options, a CFML-to-Java mapping table, and a before-and-after conversion of a cfquery page into Spring Boot. It is the ColdFusion chapter of our broader legacy software modernization guide.

Key takeaways

  • Adobe ColdFusion 2021 left core support on November 10, 2025; its extended support, which carries no security patches, ends November 10, 2026.
  • Lucee is a credible way to stay on CFML at lower cost, but it is not a drop-in clone of Adobe ColdFusion and does not fix talent or architecture problems.
  • The hard parts of a ColdFusion to Java migration are semantic: scope lookup order, case-insensitive comparisons, 1-based arrays, auto-casting and runtime evaluation.
  • A cfquery without cfqueryparam is the classic SQL injection source in CFML; translation to parameterized Java closes it systematically.
  • Prove equivalence with recorded traffic and rendered-HTML comparison before cutover, not after.

What is a ColdFusion migration?

A ColdFusion migration is the move of an application written in CFML (ColdFusion Markup Language) off its current runtime. In practice, most of these decisions start from the same place: an application built 10 to 20 years ago, still central to the business, running on a release Adobe no longer patches, and maintained by a team of two or three. From there the realistic destinations are a supported Adobe release, an alternative CFML engine such as Lucee, or a move off CFML entirely, most often to Java on Spring Boot with the application’s behavior preserved either way.

The three variants carry very different risks. An upgrade keeps your code and changes the engine. A move to Lucee keeps the language but changes the implementation. A conversion to Java changes language, runtime, frameworks and templating at once. Most of this guide is about the third case, because that is where the engineering problems are.

ColdFusion end of life: Adobe and Lucee support status in 2026

Adobe lists general availability, end of core support and end of extended support for every release in its product end-of-life matrix. For the releases still found in production:

Adobe ColdFusion releaseGeneral availabilityEnd of core supportEnd of extended support
ColdFusion 2016Feb 16, 2016Feb 17, 2021Feb 17, 2022
ColdFusion 2018Jul 12, 2018Jul 13, 2023Jul 13, 2024
ColdFusion 2021Nov 11, 2020Nov 10, 2025Nov 10, 2026
ColdFusion 2023May 17, 2023May 16, 2028May 16, 2029
ColdFusion 2025Feb 25, 2025Feb 26, 2030N/A

Two details matter more than the dates. First, extended support is not a security program. Adobe's ColdFusion 2021 end-of-support notice describes it as best-effort help "intended to assist customers in migration to a core supported version" that "DOES NOT INCLUDE security patches or hot fixes." If you run ColdFusion 2021 today, Adobe no longer ships security fixes for it, and even the extended window closes in under a month.

Second, the same notice states that perpetual licenses have been discontinued in favor of subscriptions. For teams that bought ColdFusion once and ran it for a decade, a sunk cost becomes a recurring line item, which is often what starts the "should we still be on ColdFusion at all" conversation.

Lucee release status

Lucee is the open-source CFML engine maintained by the non-profit Lucee Association Switzerland. Per the Lucee 7.0.4.34 stable release announcement of June 4, 2026, Lucee 7.0 is the active stable line, 6.2 is the long-term-support line receiving only critical and regression fixes, and 5.4 LTS reached end of life at the end of 2025. The installers and Docker images bundle Java 21. Teams that moved to Lucee 5 now have their own upgrade to plan.

Why teams leave ColdFusion

Four pressures usually drive a ColdFusion migration decision, in combination.

Licensing and cost predictability. Subscription licensing plus a forced upgrade every few years make Adobe ColdFusion a recurring budget item. Lucee removes the license fee but not the upgrade cadence.

The talent pool. Good ColdFusion developers exist, but hiring them gets harder every year, and the people who know your application best are often closest to retirement. Our post on the legacy code talent shortage covers this across languages. Java is a default skill for graduates and contractors.

Security exposure. ColdFusion servers are often internet-facing, and attackers know it. CISA has added several ColdFusion flaws to its Known Exploited Vulnerabilities catalog, including the deserialization bugs CVE-2023-29300 and CVE-2023-38203 in its January 2024 KEV update. CISA advisory AA23-339A describes attackers using CVE-2023-26360 for initial access to at least two public-facing servers at a federal civilian agency in mid-2023; one was running ColdFusion 2016, already out of support. It has not stopped: Adobe's APSB26-68 bulletin of June 30, 2026 fixed a batch of critical ColdFusion 2023 and 2025 flaws and states that one, CVE-2026-48282, "has been exploited in the wild in limited attacks." Our post on legacy system security risk explains why security debt tends to become the deadline.

Architecture. Many CFML estates are page-centric: one .cfm per screen, SQL inline, HTML and JavaScript interleaved with logic. That is hard to test, hard to containerize and hard to expose as APIs.

There are good reasons to stay, too. For a small, stable, patched internal application with a team that likes CFML, upgrading to ColdFusion 2025 or moving to Lucee is often the cheapest responsible choice. Convert to Java when the cost of staying exceeds the cost and risk of conversion.

CFML internals that make a ColdFusion to Java migration hard


CFML internals that make a ColdFusion to Java migration hard

ColdFusion compiles CFML to Java bytecode and runs on the JVM, so people assume conversion is mechanical. In ColdFusion vs Java terms, the runtime is shared; the language semantics are not.

Tag-based CFML vs CFScript

CFML has two syntaxes that can be mixed in one file: tags that look like HTML and CFScript that looks like JavaScript.

<!--- tag syntax --->

<cfset discount = 0>

<cfif customer.tier EQ "gold">

<cfset discount = 0.1>

<cfelseif customer.tier EQ "silver">

<cfset discount = 0.05>

</cfif>

<cfscript>

// CFScript syntax

discount = 0;

if (customer.tier == "gold") {

discount = 0.1;

} else if (customer.tier == "silver") {

discount = 0.05;

}

</cfscript>

Both share one trap: EQ and == compare strings case-insensitively, so "GOLD" == "gold" is true. A naive translation to tier.equals("gold") changes behavior for every record stored in a different case. The faithful target is equalsIgnoreCase; CFML's compare() is the case-sensitive form. A ColdFusion parser must also handle the SQL inside cfquery, the HTML around cfoutput and the page's JavaScript, all in one file.

Scopes and the unscoped lookup order

CFML variables live in scopes: variables, request, session, application, form, url, cgi, cookie, client, server, plus local, arguments and this inside components. When code omits the scope prefix, ColdFusion searches. Adobe's documentation on scopes gives the order: Local, Arguments, Thread local, Query (inside query loops), Thread, Variables, CGI, Cffile, URL, Form, Cookie, Client.

Consider #userId# in a page with no variables.userId: ColdFusion finds url.userId, form.userId or cookie.userId, depending on the request. A Java translation must either reproduce the search or prove, by data-flow analysis across the include chain, which scope supplies the value on each path. session, application, request and server are never searched, so every access to them is explicit and easier to translate.

Application.cfc lifecycle

Application.cfc holds settings in the this scope (this.name, this.sessionManagement, this.datasource, this.mappings) and event methods: onApplicationStart, onApplicationEnd, onSessionStart, onSessionEnd, onRequestStart, onRequest, onRequestEnd, onError and onMissingTemplate.

onApplicationStart usually loads configuration and cached lookups into the application scope; that becomes @Configuration beans and an ApplicationReadyEvent listener. onRequestStart is usually an authentication gate; that becomes a Spring Security filter chain or a HandlerInterceptor. onSessionEnd becomes an HttpSessionListener, and if sessions move to Spring Session on Redis or JDBC, expiry events behave differently and must be retested. Older code may still use Application.cfm and OnRequestEnd.cfm, which run as included templates.

cfquery, cfqueryparam and query of queries

cfquery sends SQL to a named datasource and returns a query object: a 1-based result set you can loop over, sort and pass around. Values written as #...# are pasted into the SQL text; cfqueryparam binds them as parameters instead. Adobe's cfqueryparam reference calls it "essential for preventing SQL injection" and notes it cannot protect dynamic table or column names, which need an allow-list.

Query of queries (<cfquery dbtype="query">) runs a restricted SQL dialect against a query object already in memory. In Java it becomes stream operations or, usually better, a single database query that does the work where the data lives. The dialect has its own rules for case sensitivity and type coercion, so translated results must be checked against the original.

CFCs as components

ColdFusion Components (.cfc files) are CFML's classes, with properties, methods, inheritance via extends and an access level per method. access="remote" exposes a method over HTTP, so orders.cfc?method=getOrders is callable straight from a browser. In Java those become explicit @RestController endpoints with their own authorization, which is a translation step and a security review at once.

CFCs cached in application or session scope map to singleton or session-scoped Spring beans. Check thread safety: an application-scoped CFC that writes to its own variables scope is shared mutable state, and was already a latent bug in ColdFusion.

Dynamic evaluation: Evaluate, cfinclude and dynamic variable names

CFML allows code that is only fully defined at runtime:

  • Evaluate("form.qty_" & i) evaluates a string as an expression.
  • form["qty_#i#"] and setVariable("total_" & region, 0) build variable names dynamically.
  • <cfinclude template="#url.page#.cfm"> picks the template at runtime and shares the caller's variables scope.
  • iif() with de() delays evaluation of string expressions.

Most uses follow patterns static analysis can resolve: dynamic names over a known prefix become a Map or indexed list, and an include chosen from a closed set of values becomes a dispatch table. Open-ended evaluation, where the expression comes from a database or user input, cannot be translated faithfully without an interpreter, and is usually a security hole anyway. Those sites need an engineer's decision.

Typeless variables and auto-casting

CFML has no declared types. "42" is a number when you add to it, "yes" is a boolean in an if, and "2026-10-13" is a date when passed to dateAdd. Struct keys are case-insensitive, arrays start at 1, arithmetic is double precision unless you use precisionEvaluate(), and by default a database null reads as an empty string.

Each becomes an explicit decision in Java: int, long or BigDecimal; String or LocalDate; HashMap or a case-insensitive TreeMap; zero-based indexes; null handling at every read. The right type comes from how a variable is used across the code and from the database schema, not from guessing per line.

Built-in tags: cfmail, cfdocument, cfspreadsheet

ColdFusion's productivity came from built-ins that hide whole libraries: cfmail, cfdocument (HTML to PDF), cfspreadsheet (Excel), cfhttp, cfschedule, cfthread. Each maps to a Java library, but not identically. PDF engines differ in CSS support and pagination, so cfdocument output must be compared page by page.

Custom tags

<cf_header title="Orders"> runs header.cfm with an attributes scope, a caller scope pointing into the calling page and a thisTag scope reporting start or end execution mode. Tags can also be called with cfmodule or cfimport. The caller scope is the hard part: a tag that writes caller.result mutates the caller's variables, a side effect that becomes an explicit return value or model attribute in Java.

ColdFusion replacement options: Lucee, rewrite or translate

A ColdFusion migration has five realistic dispositions, and most estates use more than one.

OptionStays on CFMLCode changeMain riskLicensingTalent and architecture
Upgrade to Adobe ColdFusion 2025YesLowDeprecated tags, changed defaultsAdobe subscriptionUnchanged
Move to Lucee 7YesLow to moderateEngine differences vs AdobeOpen sourceUnchanged
Manual rewrite in JavaNoTotalLost business rules, long parallel runOpen source stackFixed, slowly
Automated translation to JavaNoTotal, generatedConstructs needing engineer reviewOpen source stackFixed; structure mirrors original at first
Retain and hardenYesMinimalOngoing CVE exposure, shrinking teamUnchangedUnchanged

Lucee fits when the main pain is Adobe licensing, the application is healthy and the team wants to keep writing CFML. It runs most CFML well, but some tags, functions and admin features differ from Adobe's, so budget a full regression pass. It does not change who can maintain the code.

A rewrite fits when the application's purpose has changed enough that preserving behavior is not the goal. It is wrong when the code encodes years of undocumented business rules, which CFML pages, mixing rules, SQL and markup, very often do. If you rewrite, extract those rules first; see our guide to legacy code analysis and business rules extraction.

Translation to Java fits a large, business-critical, still-correct application that has to leave CFML for talent, security or platform reasons. Typical targets:

  • Spring Boot with Spring MVC and Thymeleaf or JSP, closest to the page-per-screen model. JSP keeps markup more literally; Thymeleaf escapes output by default and is the better long-term choice.
  • Spring Boot REST APIs plus a single-page front end, when the UI is being redesigned anyway.

Both are covered on our ColdFusion to Java migration service page. If your estate also has desktop clients, the companion VB6 to .NET migration guide applies the same reasoning to Visual Basic 6.

CFML to Java mapping table

How each construct maps when you convert ColdFusion to Java, and where a literal translation breaks:

CFML constructJava / Spring Boot targetGotcha
.cfm page@Controller method plus Thymeleaf or JSP viewSeparate logic from markup
.cfc component@Service or @Repository classApplication-scoped CFCs must be thread-safe
access="remote" method@RestController endpointWas publicly callable; add authorization
Application.cfc settingsapplication.yml, @ConfigurationDatasources and mappings move to config
onRequestStart / onErrorSecurity filter chain, @ControllerAdviceFilter and interceptor ordering
variables scopeLocals and fieldsShared across cfinclude chains
request scopeRequest attributes, @RequestScope beanVisible to custom tags
session scopeHttpSession or Spring SessionClustered sessions need serialization
application scopeSingleton beansConcurrent writes need locking
form / url scope@RequestParam, @ModelAttributeUnscoped reads search both
cgi / client scopeHttpServletRequest / database storeProxies change remote_addr
cfquery + cfqueryparamJdbcClient, NamedParameterJdbcTemplate, JPAColumn names case-insensitive in CFML
Query of queriesStreams, or move logic into SQLDifferent dialect and coercion
cftransaction@TransactionalCheck isolation levels
Arrays, cfloopList, for loopCFML arrays are 1-based
EQ, == on stringsequalsIgnoreCaseCFML comparison ignores case
StructTyped class or case-insensitive TreeMapKeys are case-insensitive
Evaluate(), setVariable()Map lookup or dispatch tableOpen-ended evaluation needs review
cfinclude, custom tagsMethod calls, view fragmentscaller writes become return values
cflock / cfthreadReentrantLock / @Async, ExecutorServiceNamed locks are server-wide
cfmailJavaMailSender (Jakarta Mail)Spooling and retry
cfdocument / cfspreadsheetHTML-to-PDF library / Apache POIRendering and cell formats differ
cfhttpRestClient or WebClientTimeouts, redirects
cfschedule@Scheduled or QuartzTasks often live only in CF Administrator
createObject("java", ...)Direct Java callUsually one to one

Spring's JDBC documentation describes JdbcTemplate, NamedParameterJdbcTemplate and, since Spring Framework 6.1, the fluent JdbcClient with positional and named parameters. These keep hand-written SQL visible and reviewable; JPA fits mainly where code already uses ColdFusion ORM.

Before and after: converting a cfquery page to Spring Boot

A typical CFML list page: filters from the URL, a query, a table with a running total. It is also vulnerable in a way that is easy to miss.

Before: orders.cfm

<!--- orders.cfm --->

<cfparam name="url.status" default="OPEN">

<cfparam name="url.customerId" default="0">

<cfparam name="url.sort" default="order_date">

<cfquery name="qOrders" datasource="#application.dsn#">

SELECT o.order_id, o.order_date, o.total_amount, c.customer_name

FROM orders o

JOIN customers c ON c.customer_id = o.customer_id

WHERE o.status = '#url.status#'

<cfif url.customerId GT 0>

AND o.customer_id = #url.customerId#

</cfif>

ORDER BY #url.sort#

</cfquery>

<cfset grandTotal = 0>

<table>

<tr><th>Order</th><th>Date</th><th>Customer</th><th>Total</th></tr>

<cfoutput query="qOrders">

<cfset grandTotal = grandTotal + total_amount>

<tr>

<td>#order_id#</td>

<td>#dateFormat(order_date, "mm/dd/yyyy")#</td>

<td>#customer_name#</td>

<td>#dollarFormat(total_amount)#</td>

</tr>

</cfoutput>

<cfoutput>

<tr><td colspan="3">Total</td><td>#dollarFormat(grandTotal)#</td></tr>

</cfoutput>

</table>

What is wrong with it:

  • url.status sits inside single quotes. Adobe ColdFusion automatically escapes single quotes in variables interpolated into cfquery string literals, which blunts the obvious attack, so developers learn to skip cfqueryparam.
  • url.customerId is unquoted. Quote escaping does nothing, and 1 OR 1=1 is injected as SQL. The GT 0 check does not stop it, because CFML falls back to string comparison for non-numeric values.
  • url.sort is an identifier. cfqueryparam cannot bind it; it needs an allow-list in either language.
  • #customer_name# is output unescaped, a stored cross-site scripting bug.

After: repository, controller and view

The repository uses JdbcClient with named parameters and an allow-list for the sort column:

public record OrderRow(long orderId, LocalDate orderDate,

String customerName, BigDecimal totalAmount) {}

@Repository

public class OrderRepository {

private static final Map<String, String> SORT_COLUMNS = Map.of(

"order_date", "o.order_date",

"order_id", "o.order_id",

"customer_name", "c.customer_name",

"total_amount", "o.total_amount");

private final JdbcClient jdbc;

public OrderRepository(JdbcClient jdbc) {

this.jdbc = jdbc;

}

public List<OrderRow> findOrders(String status, long customerId, String sort) {

String orderBy = SORT_COLUMNS.getOrDefault(sort.toLowerCase(Locale.ROOT), "o.order_date");

StringBuilder sql = new StringBuilder("""

SELECT o.order_id, o.order_date, o.total_amount, c.customer_name

FROM orders o

JOIN customers c ON c.customer_id = o.customer_id

WHERE o.status = :status

""");

Map<String, Object> params = new HashMap<>();

params.put("status", status);

if (customerId > 0) {

sql.append(" AND o.customer_id = :customerId");

params.put("customerId", customerId);

}

sql.append(" ORDER BY ").append(orderBy);

return jdbc.sql(sql.toString())

.params(params)

.query((rs, rowNum) -> new OrderRow(

rs.getLong("order_id"),

rs.getObject("order_date", LocalDate.class),

rs.getString("customer_name"),

rs.getBigDecimal("total_amount")))

.list();

}

}

The controller replaces cfparam with typed request parameters and totals with BigDecimal:

@Controller

public class OrderController {

private final OrderRepository orders;

public OrderController(OrderRepository orders) {

this.orders = orders;

}

@GetMapping("/orders")

public String list(@RequestParam(defaultValue = "OPEN") String status,

@RequestParam(defaultValue = "0") long customerId,

@RequestParam(defaultValue = "order_date") String sort,

Model model) {

List<OrderRow> rows = orders.findOrders(status, customerId, sort);

BigDecimal grandTotal = rows.stream()

.map(OrderRow::totalAmount)

.filter(Objects::nonNull)

.reduce(BigDecimal.ZERO, BigDecimal::add);

model.addAttribute("orders", rows);

model.addAttribute("grandTotal", grandTotal);

return "orders";

}

}

The Thymeleaf view escapes output by default with th:text:

<!-- templates/orders.html -->

<table>

<tr><th>Order</th><th>Date</th><th>Customer</th><th>Total</th></tr>

<tr th:each="o : ${orders}">

<td th:text="${o.orderId()}">1001</td>

<td th:text="${#temporals.format(o.orderDate(), 'MM/dd/yyyy')}">01/31/2026</td>

<td th:text="${o.customerName()}">Acme</td>

<td th:text="${#numbers.formatCurrency(o.totalAmount())}">$0.00</td>

</tr>

<tr>

<td colspan="3">Total</td>

<td th:text="${#numbers.formatCurrency(grandTotal)}">$0.00</td>

</tr>

</table>

Gotchas in this conversion

Each of these changes visible behavior and must be preserved or accepted deliberately:

  • Date masks differ. In CFML dateFormat, mm is month. In Java's DateTimeFormatter, mm is minutes and MM is month. Copying the mask prints minutes where the month should be.
  • Negative currency differs. dollarFormat puts negative amounts in parentheses; Java's default US currency format uses a minus sign. If credit notes appear here, write a custom formatter.
  • Totals may differ by a cent. CFML adds doubles; Java adds BigDecimal. The Java result is correct, but equivalence tests must expect the difference.
  • Bad input fails differently. customerId=abc used to produce a ColdFusion error or an injected query; Spring now returns 400 Bad Request. Update monitoring and any client that relied on the old error.
  • Escaping changes the HTML. Thymeleaf emits &amp; for &. Browsers show the same text, but byte-level comparisons flag it, and columns that stored intentional HTML now display tags. Decide per column.
  • The schema decides types. getObject("order_date", LocalDate.class) suits a DATE column; a timestamp column needs LocalDateTime.
  • Sort keys were case-insensitive. Bookmarks with sort=ORDER_DATE keep working because the repository lowercases the key first.

Multiply seven gotchas by every list page in an estate: hand conversion gets most of them right most of the time, which is not the same as all of them, every time.

Security hardening as a migration benefit

A ColdFusion migration to Java is the cheapest moment you will have to remove whole classes of vulnerability, because every line is touched anyway. The OWASP SQL Injection Prevention Cheat Sheet names parameterized queries as the primary defense and notes ColdFusion supports them. In CFML the problem is not capability; it is consistency. Use the migration to enforce:

  • Every interpolated cfquery value becomes a bind parameter, including numbers and IN lists (list="yes" becomes a collection parameter).
  • Every dynamic identifier (ORDER BY, table, column) passes an allow-list.
  • Output is escaped by default, with reviewed exceptions for trusted HTML.
  • Every access="remote" method is removed or given explicit authorization.
  • Evaluate(), iif() and dynamic cfinclude driven by request data are removed.
  • Uploads (cffile action="upload") get server-side type checks and storage outside the web root.
  • Datasource credentials move from the CF Administrator to a secrets manager.

The Java stack has CVEs too, so it still needs a patch process and a software bill of materials.

A step-by-step ColdFusion migration plan

This sequence works whether the target is Lucee or Java:

  • Inventory everything, not just .cfm files. Templates, CFCs, custom tags, Application.cfc, mappings, datasources, scheduled tasks, mail settings and Java libraries in ColdFusion's lib folder. Much of it lives in the CF Administrator, not in source control.
  • Measure what actually runs. Web logs show which templates are requested. Pages unused for a year are candidates for retirement, not translation.
  • Count dynamic constructs. Evaluate, setVariable, dynamic includes, query-of-queries, unscoped reads and remote methods predict ColdFusion migration effort better than line count.
  • Choose a target per application. A large estate may move its portal to Java and its back-office tools to Lucee.
  • Record behavior first. Capture production requests, responses and database state as your regression oracle. Our guide to proving functional equivalence after a migration covers comparing outputs that legitimately differ without hiding real defects.
  • Convert in slices behind a reverse proxy. ColdFusion and Spring do not share session memory, so move authentication to SSO or a token both sides validate first. Our post on strangler fig migrations that stall explains how to keep coexistence short.
  • Run in parallel and compare responses, database writes and generated PDFs and spreadsheets.
  • Cut over and decommission the ColdFusion server, its Administrator and datasources. An unpatched server left running "just in case" is exactly what CISA's advisories describe attackers finding.

How automated translation handles CFML


How automated translation handles CFML

Modernize Software's DMS (Design Maintenance System) transformation engine treats a ColdFusion migration as a compiler problem. According to the ColdFusion service page, DMS parses each .cfm file as a single unit, covering CFML tags, CFScript, embedded SQL, HTML and inline JavaScript, then applies rewrite rules that map each construct to Spring Boot. Because the engine is deterministic, the same input gives the same output every run, so a rule fix applies everywhere its pattern occurs.

The internals above are what any rule set has to get right:

  • Type inference. Modernize Software reports that DMS infers Java types from usage across the codebase and from the database schema, choosing integer widths and BigDecimal where needed instead of defaulting to Object or String.
  • Scope resolution. Whole-program data-flow analysis, which DMS performs, is what determines which scope supplies an unscoped variable on each path through a cfinclude chain.
  • Semantic fidelity. Case-insensitive EQ, 1-based loop bounds and null-as-empty-string behavior are encoded once as rules, so they are consistent across thousands of templates.
  • Flagging, not guessing. Modernize Software states that constructs which cannot be translated deterministically are flagged for engineer review; in CFML, that means Evaluate on database strings or unbounded include paths.

The limits are real. Translation preserves behavior, including structure: a page-per-screen application becomes controller-per-screen, which is better than CFML but not a redesigned domain model. Visual built-ins such as cfdocument need human review of rendered output. GenAI helps around the translation with documentation, tests and rule summaries, but does not write the translated code; see why GenAI alone is not enough for modernization. For estates built on in-house frameworks or generators, the engine can be extended, as described in our post on building a custom source-to-source translator.

Illustrative scenario: an intranet on ColdFusion 2021

This is a hypothetical example, not a client engagement.

A regional insurer runs a claims intranet on ColdFusion 2021: about 1,800 templates, 300 CFCs and 40 custom tags. Core support has ended, the security team has flagged the server, and the two developers who know the code have announced retirement dates.

The inventory finds a fifth of the templates unrequested in 18 months; they are archived. Most Evaluate calls follow the form["field_#i#"] pattern over repeating rows and translate to indexed collections. A handful evaluate formulas stored in a rules table; those are rewritten by hand as a small tested evaluator with an allow-listed grammar. Nearly a third of cfquery blocks interpolate an unparameterized value.

The team picks Spring Boot with Thymeleaf because adjusters do not want a new UI, moves authentication to corporate SSO, and translates in four slices behind a reverse proxy. Each slice is verified by replaying recorded traffic and diffing rendered HTML, with known differences (escaped ampersands, negative currency) accepted after review.

The same discipline, automated conversion plus equivalence evidence, underlies Modernize Software's public case of a Fujitsu NetCOBOL application with embedded SQL converted to C# in under six months. For the cross-language version of this playbook, see our end-to-end guide to modernizing legacy software.

Frequently asked questions

1. Is ColdFusion end of life?

ColdFusion as a product is not end of life. Adobe supports ColdFusion 2023 (core support to May 16, 2028) and ColdFusion 2025 (core support to February 26, 2030). Individual releases do expire: ColdFusion 2021 core support ended November 10, 2025, and its extended support, which includes no security patches, ends November 10, 2026. ColdFusion 2018 and older are fully unsupported.

2. Is Lucee compatible with Adobe ColdFusion?

Lucee runs most CFML written for Adobe ColdFusion, but it is a separate implementation, not a clone. Some tags, functions, administrator settings and edge-case behaviors differ, and some Adobe-only features are missing. Small, conventional applications often move with minor fixes; large ones need a full regression pass. Treat a move to Lucee as a migration with its own test plan.

3. Can you convert ColdFusion to Java automatically?

Yes, most CFML can be converted automatically by a parser-based translator that handles tags, CFScript, SQL and HTML together and infers types from usage and the database schema. Some constructs, such as Evaluate() on strings from a database or includes with unbounded runtime paths, cannot be translated deterministically and need an engineer's decision. A good tool flags those instead of guessing.

4. Does ColdFusion run on Java?

Yes. Adobe ColdFusion and Lucee compile CFML to Java bytecode and run on the JVM, and CFML can call Java classes through createObject("java", ...). That makes Java a natural target and existing Java libraries reusable. It does not make conversion trivial: scope rules, case-insensitive comparisons, 1-based arrays and typeless auto-casting all have to be translated explicitly.

5. What is the best replacement for ColdFusion?

It depends on why you are leaving. If the issue is Adobe licensing and the team likes CFML, Lucee is the lowest-effort replacement. If the issues are hiring, security response and architecture, Java with Spring Boot is the most common target, since ColdFusion already runs on the JVM. C# and ASP.NET Core are reasonable where an organization has standardized on Microsoft.

6. How long does a ColdFusion to Java migration take?

It depends more on dynamic constructs, frameworks and test coverage than on line count. As a reference point, Modernize Software's ColdFusion service page estimates four to eight months for a 100,000-line codebase and 12 to 24 months above 500,000 lines using automated translation. A manual rewrite must also budget time to rediscover business rules before it can reimplement them.

Start with a ColdFusion codebase assessment

The fastest way to size a ColdFusion migration is to measure your code: templates, CFCs, custom tags, dynamic constructs and unparameterized queries. Modernize Software offers a free codebase assessment that returns a complexity report, an automation estimate and a rough timeline in about five business days. Request your assessment, or read how we approach converting ColdFusion applications to Spring Boot.