NPE in `ConfigImpl` on clean boot when Event Gateways auto-start before `Component.cfc` is deployed

Following up on the warmup thread (docker-build-warmup-behaviour/17547) and LDEV-6472, there is a separate related issue on clean startup when extensions install automatic event gateways (e.g. Quartz Scheduler).

Disclaimer: I used AI to assist in this debugging/report

During initial context initialization, Lucee throws a NullPointerException:

ERROR [server.gateway] Cannot invoke "lucee.commons.io.res.Resource.getCanonicalPath()" because "a" is null
lucee.runtime.exp.NativeException: Cannot invoke "lucee.commons.io.res.Resource.getCanonicalPath()" because "a" is null
	at lucee.runtime.config.ConfigImpl.getBaseComponentPageSource(ConfigImpl.java:2296)
	at lucee.runtime.config.ConfigWebImpl.getBaseComponentPageSource(ConfigWebImpl.java:464)
	at lucee.runtime.config.ConfigWebHelper.getBaseComponentPage(ConfigWebHelper.java:254)
	at lucee.runtime.config.ConfigWebHelper.getBaseComponentInstance(ConfigWebHelper.java:277)
	at lucee.runtime.config.ConfigWebImpl.getBaseComponentInstance(ConfigWebImpl.java:1614)
	at lucee.runtime.ComponentImpl.init(ComponentImpl.java:480)
	at org.lucee.extension.quartz.quartzgateway_cfc$cf.initComponent(/org/lucee/extension/quartz/QuartzGateway.cfc)
	at org.lucee.extension.quartz.quartzgateway_cfc$cf.newInstance(/org/lucee/extension/quartz/QuartzGateway.cfc:1)
	at lucee.runtime.ComponentPageImpl.call(ComponentPageImpl.java:161)
	at lucee.runtime.gateway.GatewayEngineImpl.getCFC(GatewayEngineImpl.java:440)
	at lucee.runtime.gateway.GatewayEngineImpl.autoStart(GatewayEngineImpl.java:210)
	at lucee.runtime.config.ConfigFactoryImpl.newInstanceWeb(ConfigFactoryImpl.java:229)

Root Causes

Tracing through the Lucee 7 codebase, this is caused by three compounding issues:

1. Inverted Lifecycle Order (ConfigFactoryImpl.java:345 vs 348)

345: load(config, root, false, doNew, essentialOnly); 
     // ↳ _loadExtensionBundles() downloads/installs extensions -> registers Quartz gateway
...
348: createContextFiles(configDir, config, doNew);
     // ↳ line 4030 calls ComponentFactory.deploy() -> extracts Component.cfc from core.lco
  • In load() (line 345), Lucee installs extension bundles. Extensions registering event gateways get auto-started when newInstanceWeb() executes ((GatewayEngineImpl) configWeb.getGatewayEngine()).autoStart().
  • QuartzGateway.cfc extends Component.cfc.
  • However, ComponentFactory.deploy() (which extracts base components like Component.cfc from core.lco into components/org/lucee/cfml/) is not called until createContextFiles() at line 348.
  • Because the gateway starts before core components are unpacked, the Component.cfc lookup fails.

2. Typo in ConfigImpl.java:2294-2296

When Lucee attempts to format the component lookup error message, it hits this block:

Resource a = m.getArchive();
String archive = m.getStrArchive();
if (p != null) { // <-- Bug: checks physical resource 'p' instead of archive 'a'
    archive = a.getCanonicalPath() + " (" + m.getStrArchive() + ")"; // Throws NPE because 'a' is null!
}
  • When resolving against a physical-only mapping (such as /lucee-server/ which has "primary": "physical" and "archive": ""), archive a is null.
  • The code checks if (p != null) instead of if (a != null).
  • Because p exists, it calls a.getCanonicalPath() on null, throwing the raw NullPointerException and completely masking the underlying missing component error.

3. Unnecessary Gateway Auto-Start in Warmup Mode

  • In LUCEE_ENABLE_WARMUP=true (or any headless build mode), external resources (databases, message queues, mail servers) do not exist.
  • Auto-starting event gateways during warmup is counter-productive: it spawns background threads and attempts connections against non-existent infrastructure before immediately shutting down.

Suggested Fixes

  1. Suppress Gateway Auto-Start During Warmup: When LUCEE_ENABLE_WARMUP=true, skip ((GatewayEngineImpl) configWeb.getGatewayEngine()).autoStart() in ConfigFactoryImpl.java:229. Gateways have no purpose running during a build warmup pass.
  2. Fix NPE in ConfigImpl.java:2296: Change if (p != null) to if (a != null).
  3. Ensure Core Components Deploy First: Run ComponentFactory.deploy() before _loadExtensionBundles() so base components exist whenever extensions load.

Thanks for the detailed report. Filed this as a separate bug (not LDEV-6472):

https://luceeserver.atlassian.net/browse/LDEV-6481

Thanks @GianTschingt and @micstriit for PR #2886 in 7.1.2.25+!

Testing a clean boot on the latest 7.1.2 snapshot (7.1.2.173-SNAPSHOT), I can confirm the NullPointerException in ConfigImpl.getBaseComponentPageSource() is completely resolved.

However, as Gian noted on LDEV-6481:

“The startup order is not changed by this PR… so a missing Component.cfc gives null and a log entry instead of an NPE.”

Because the lifecycle ordering was not changed, clean boots (such as fresh Docker container startup or build-time warmup) now log ~10 consecutive errors when Quartz installs its gateway:

ERROR [server.application] could not load the base component Component, it was not found in any of the component mappings.The following component mappings are available [/opt/lucee/server/lucee-server/context/components ({lucee-config}/components/):null]

A fraction of a second later, createContextFiles() runs ComponentFactory.deploy(), unpacking Component.cfc from core.lco, and subsequent requests proceed normally. But during the initial gateway registration pass, Component.cfc genuinely does not exist on disk yet.

Could we raise a follow-up ticket for the lifecycle ordering / gateway auto-start?

Specifically, Gian’s original proposed fixes on the ticket:

  1. Ensure Core Components Deploy First: Run ComponentFactory.deploy() before load() installs extension bundles, or have getBaseComponentPageSource() trigger deployment if Component.cfc is missing.
  2. Suppress / Defer Gateway Auto-Start: Defer event gateway auto-start until the server finishes initializing, and skip gateway auto-start entirely when LUCEE_ENABLE_WARMUP=true.

For now, we have to keep a Dockerfile workaround to manually extract resource/component from core.lco into components/ before Lucee boots to keep clean startups clean.

Thanks @philsw, good analysis. On a fresh install the bundled extensions (Quartz in your case) are installed before the core base components such as Component.cfc are extracted, so the first gateway/component loads fail with “could not load the base component”. We’ll keep extensions loading first, since the config itself can point to an extension (an S3 mapping, for example), but we’ll extract the core base components and the gateway base CFCs before the extensions install. Filed as LDEV-6666, fix for 7.1 is in lucee/Lucee#3224. I’ll report back once there’s a snapshot to test.