From dd2bd71cc2b72e304322722ab77b2accf21e2339 Mon Sep 17 00:00:00 2001 From: Deepak Dixit Date: Mon, 15 Jun 2026 10:36:38 +0530 Subject: [PATCH] Fix NPE in DeferredHitInfoFlush when Jetty request is recycled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In makeAhiValue, parameters may contain ContextStack values (which implement Map). The previous check order called ObjectUtilities.isEmpty(value) before value instanceof Map, causing ContextStack.size() to traverse the stack and hit a recycled Jetty EE11 request object in a background thread — resulting in a NullPointerException from ServletApiRequest.getRequest() returning null. Fix by checking instanceof Map/Collection before isEmpty so ContextStack values short-circuit without calling size(). Also add a defensive NullPointerException catch in ServletRequestContainer.getAttributeNames() to mirror the existing IllegalStateException guard in HttpSessionContainer. --- .../groovy/org/moqui/impl/context/ContextJavaUtil.java | 2 +- .../src/main/java/org/moqui/util/WebUtilities.java | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/framework/src/main/groovy/org/moqui/impl/context/ContextJavaUtil.java b/framework/src/main/groovy/org/moqui/impl/context/ContextJavaUtil.java index bf86d9768..f3054aec4 100644 --- a/framework/src/main/groovy/org/moqui/impl/context/ContextJavaUtil.java +++ b/framework/src/main/groovy/org/moqui/impl/context/ContextJavaUtil.java @@ -268,7 +268,7 @@ EntityValue makeAhiValue(ExecutionContextFactoryImpl ecfi) { StringBuilder ps = new StringBuilder(); for (Map.Entry pme: parameters.entrySet()) { Object value = pme.getValue(); - if (value == null || ObjectUtilities.isEmpty(value) || value instanceof Map || value instanceof Collection) continue; + if (value == null || value instanceof Map || value instanceof Collection || ObjectUtilities.isEmpty(value)) continue; String key = pme.getKey(); if (key != null && key.contains("password")) continue; if (ps.length() > 0) ps.append(", "); diff --git a/framework/src/main/java/org/moqui/util/WebUtilities.java b/framework/src/main/java/org/moqui/util/WebUtilities.java index fb68dcf51..dcf13454c 100644 --- a/framework/src/main/java/org/moqui/util/WebUtilities.java +++ b/framework/src/main/java/org/moqui/util/WebUtilities.java @@ -450,7 +450,15 @@ default List getAttributeNameList() { public static class ServletRequestContainer implements AttributeContainer { ServletRequest req; public ServletRequestContainer(ServletRequest request) { req = request; } - @Override public Enumeration getAttributeNames() { return req.getAttributeNames(); } + @Override public Enumeration getAttributeNames() { + try { + return req.getAttributeNames(); + } catch (NullPointerException e) { + // Jetty EE11 recycles the underlying request object after completion; guard against background thread access + logger.warn("Tried getAttributeNames() on recycled request: " + e.toString()); + return emptyStringEnum; + } + } @Override public Object getAttribute(String name) { return req.getAttribute(name); } @Override public void setAttribute(String name, Object value) { if (!testSerialization(name, value)) return;