Server.cfc fails to resolve in Server Context without custom CFConfig mappings

Hi team,

Apologies in advance for the AI generated post, which of course may be completely inaccurate (but in case it isn’t, I thought it was worth posting for feedback here).

While setting up a custom Server.cfc under /opt/lucee/server/lucee-server/context/context/Server.cfc in Lucee 7, we ran into an unexpected path resolution failure when .CFConfig.json does not declare any custom mappings.

What Happens

When starting Lucee with a minimal .CFConfig.json that defines no custom mappings, Lucee fails to load Server.cfc, attempting to look for it at:
{lucee-server}/context/lucee-server/Server.cfc
instead of:
{lucee-server}/context/Server.cfc

Root Cause in ConfigUtil.java

Looking at lucee.runtime.config.ConfigUtil.getPageSourceExisting:

Mapping[] thisMappings = config.getMappings();
for (int i = 0; i < thisMappings.length - 1; i++) {
    mapping = thisMappings[i];
    if ((!onlyTopLevel || mapping.isTopLevel()) && lcRealPath.startsWith(mapping.getVirtualLowerCaseWithSlash(), 0)) {
        ps = mapping.getPageSource(realPath.substring(mapping.getVirtual().length())); // Strips virtual prefix
        if (ps.exists()) return ps;
    }
}
if (useDefaultMapping) {
    if (rootApp != null) mapping = rootApp;
    else mapping = thisMappings[thisMappings.length - 1]; // Assumes the last mapping is default web root "/"

    ps = mapping.getPageSource(realPath); // DOES NOT strip prefix!
    if (ps.exists()) return ps;
}
  1. The for loop iterates up to i < thisMappings.length - 1, intentionally skipping the last element under the assumption that the final mapping is always the web root (/).
  2. In the Server Context, there is no default / root mapping. When .CFConfig.json does not specify mappings, Lucee only registers two default mappings:
    • [0] = /lucee
    • [1] = /lucee-server
  3. Because /lucee-server is at index 1 (length - 1), it is skipped by the for loop completely.
  4. Execution then falls through to useDefaultMapping, which selects thisMappings[thisMappings.length - 1] (/lucee-server) as the fallback default mapping.
  5. However, useDefaultMapping passes realPath directly to mapping.getPageSource(realPath) without stripping the virtual prefix /lucee-server.
  6. As a result, Lucee attempts to resolve physical path + /lucee-server/Server.cfc, nesting the path twice.

Workaround

Adding additional mappings to .CFConfig.json after /lucee-server (for example, /lucee/admin or /lucee/doc, matching what ships in vanilla Lucee builds) pushes /lucee-server before index length - 1. The loop then processes /lucee-server, strips the prefix, and resolves Server.cfc without error.

Suggested Fix

Rather than assuming thisMappings[thisMappings.length - 1] is always the root / mapping:

  • Check mapping.getVirtual().equals("/") to identify the root mapping, or
  • Ensure all entries are checked in the loop if no mapping with virtual path "/" exists in the configuration (as is typical in the Server Context).

@micstriit because you were so kind to read my other 2 reports… i thought you might have missed this one from a couple weeks ago… thanks in advance :slight_smile:

Hi @philsw, thanks for the reminder, and for such a detailed write-up! Your analysis checks out. I was able to reproduce it on 7.0.5.41: with no mappings (or only /lucee/ and /lucee-server/) in the server .CFConfig.json, Server.cfc is silently skipped at startup.

I’ve logged it here: Jira

Workaround until it’s fixed: keep the default /lucee/admin and /lucee/doc mappings in your server .CFConfig.json (the ones a vanilla install writes), e.g.

"mappings": {
  "/lucee/admin": { "physical": "{lucee-config}/context/admin", "archive": "{lucee-config}/context/lucee-admin.lar", "primary": "physical", "inspectTemplate": "once", "topLevel": "true", "readOnly": "true" },
  "/lucee/doc": { "archive": "{lucee-config}/context/lucee-doc.lar", "primary": "archive", "inspectTemplate": "once", "topLevel": "true", "readOnly": "true" }
}

With those in place, onServerStart() runs as expected. Thanks again!