Affected versions: v1.1 AND v1.2. Fixed in v2.0 (released
2025-12-12), where Communicator.java carries no SOAP reference at all.
Verified by reading Communicator.java at each tag on 2026-08-20 — not inferred
from release notes:
| tag |
import javax.xml.soap.SOAPMessage |
instanceof SOAPMessage in sendCall() |
| v1.1 |
present |
present (line 157) |
| v1.2 |
present |
present |
| v2.0 |
absent |
absent — sendCall() rewritten around the Radio interface |
Also present on the v1.1 tag in ocpp-common/.../utilities/SugarUtil.java.
Correction to an earlier draft of this report. We first wrote that the
branch was gone "on master (d098fc6)" and asked for a patch off v1.1. That
understated the position twice: the fix is in a released version, not only
on master, and the defect also survives in v1.2, which our first pass never
checked. Both claims are corrected above, and the ask below changed as a
result.
Environment where we hit it: OpenEMS Edge 2026.8.0 (which pins this library
via io.openems.wrapper/eu.chargetime.ocpp.bnd), Apache Felix, Eclipse
Temurin 21, Docker. OCPP-J 1.6 only — no SOAP transport configured or wanted.
Summary
Communicator.sendCall() executes an instanceof SOAPMessage test on every
outgoing call, on both the JSON and the SOAP transports:
// ocpp-common/src/main/java/eu/chargetime/ocpp/Communicator.java
32: import javax.xml.soap.SOAPMessage;
...
153: public synchronized void sendCall(String uniqueId, String action, Request request) {
154: Object call = makeCall(uniqueId, action, packPayload(request));
155:
156: if (call != null) {
157: if (call instanceof SOAPMessage) {
158: logger.trace("Send a message: {}", SugarUtil.soapMessageToString((SOAPMessage) call));
159: } else {
160: logger.trace("Send a message: {}", call);
161: }
162: }
javax.xml.soap (SAAJ) was deprecated in Java 9 and removed from the JDK in
Java 11 (JEP 320). ocpp-common/pom.xml:55-58 declares
javax.xml.soap:javax.xml.soap-api:1.4.0 at compile scope, so a plain Maven
build still works. Under OSGi, or under any deployment that does not put SAAJ on
the runtime classpath, resolving the instanceof throws.
Two things make this much worse than a missing trace log:
- The branch is on the JSON path. A pure OCPP-J deployment that never
touches SOAP still evaluates it, because instanceof requires the JVM to
resolve SOAPMessage regardless of the runtime type of call. logger.trace
being disabled does not help — the branch is evaluated before the log call.
- It is thrown inside the WebSocket handler thread. The resulting
NoClassDefFoundError propagates out of the java-websocket callback, the
listener dies, and port 8887 stops accepting. So the failure is not "one
dropped message" — the first charge point to send anything takes the whole
server down for every charge point.
Suggested fixes, cheapest first
- Move the SOAP-specific logging into the SOAP subclass.
SOAPCommunicator
already exists in ocpp-v1_6; ocpp-common should not name a SOAP type at
all. This is what master effectively did.
- If the branch must stay in
ocpp-common, gate it without loading the class:
if ("javax.xml.soap.SOAPMessage".equals(call.getClass().getName())), or
Class.forName guarded once and cached, or reflection in SugarUtil.
- At minimum, wrap the trace block in
try { … } catch (NoClassDefFoundError | LinkageError e) { logger.trace("Send a message: {}", call); } so a logging
concern cannot kill a transport.
- If SAAJ genuinely remains required at runtime, say so in the README and move
the dependency out of provided/compile-only assumptions.
What we are actually asking for
v2.0 already fixes this, so this is not a request for a fix — it is a question
about the 1.x line. Either answer is useful and we will act on it:
- (a) A
1.2.x patch release carrying only the removal of this branch, for
consumers pinned to 1.x; or
- (b) A statement that 2.0 is the only supported path, in which case
please say so plainly — downstreams pinned at 1.1 (OpenEMS among them) need to
know the 1.x line will not receive it, so they can plan the major bump.
We are not asking you to backport the rest of master. If (b), we will carry the
request downstream ourselves — see "Why this still matters to OpenEMS" below.
Related: this was predicted here in 2019
#94 — "Refactor downcast of message types in higher level components"
(closed, enhancement + good first issue) named this exact anti-pattern and
its reason: the downcast of message types couples higher-level components to the
XML/SOAP libraries, and SOAP support had by then moved into the ocpp-v1_6
subproject. This report is that observation arriving as a production failure
seven years later — the JSON path still executing a SOAP downcast, on a JDK
where the class no longer ships.
Not a duplicate: #94 is a design-cleanliness enhancement with no crash, no
NoClassDefFoundError, and no version boundary. It is the argument for why the
1.x line still deserves the removal.
Also checked and not duplicates: #19 (SOAPSyncHelper.java issue — a
forwardMessage()/promises ordering bug in the SOAP transport) and #137
(NoClassDefFoundError: eu/chargetime/ocpp/wss/WssSocketBuilder — a user who
omitted OCPP-J-*.jar from their classpath, self-resolved). Same exception type
in #137, entirely different cause: a library class missing because a jar was
left out, versus a JDK class the library references but does not declare.
Why this still matters to OpenEMS — verified on develop, 2026-08-20
This is not only a problem for our pinned tag. On OpenEMS's current default
branch:
# io.openems.wrapper/eu.chargetime.ocpp.bnd (develop, 2026-08-20)
Bundle-Version: 1.1.0
Include-Resource: @v1_6-1.1.0.jar, @OCPP-J-1.0.2.jar, @common-1.0.2.jar
Import-Package: com.sun.activation.registries, com.google.gson,
javax.xml.transform, org.java_websocket, ..., org.w3c.dom
# ^ javax.xml.soap is ABSENT
So the pin is still 1.1.0 and the import is still missing — the defect is live
on develop, not merely on the release we happened to build.
And OpenEMS can fix its half in one line. io.openems.edge.application/
EdgeApp.bndrun already uses -runproperties, for a different property:
-runproperties:\
org.osgi.service.http.port=8080,\
felix.cm.dir=c:/openems/config,\
openems.data.dir=c:/openems/data,\
org.apache.felix.eventadmin.Timeout=0,\
org.ops4j.pax.logging.DefaultServiceLog.level=WARN
Adding org.osgi.framework.bootdelegation=javax.xml.soap to that list is the
whole of the framework-side fix, in a file where the mechanism is already in
use — no new machinery, no new build step. (The SAAJ jar must still be supplied;
see the workaround below for why both halves are needed.) We will raise this
separately on the OpenEMS tracker and cross-link it here.
Workaround we ship (for anyone else hitting this)
Two halves, and both are required. Supplying the class is not enough on
OSGi, because the wrapper bundle's explicit Import-Package does not list
javax.xml.soap, so normal OSGi resolution will never wire it:
# io.openems.wrapper/eu.chargetime.ocpp.bnd — Import-Package, verbatim at 2026.8.0
Import-Package: \
com.sun.activation.registries,\
com.google.gson,\
javax.xml.transform,\
org.java_websocket,\
...
org.w3c.dom,\
# ^ no javax.xml.soap
- Supply the class. Add
javax.xml.soap-api-1.4.0.jar to the image and put
it on the boot classloader via -Xbootclasspath/a: — that is what Felix's
default org.osgi.framework.bundle.parent=boot delegates to.
- Permit the delegation. Set
org.osgi.framework.bootdelegation=javax.xml.soap, which bypasses
Import-Package resolution entirely.
Trap, and the reason this is worth writing down. The delegation must go
into -runproperties in the .bnd at build time. Passing
-Dorg.osgi.framework.bootdelegation=javax.xml.soap on the java command line
is silently ignored — no warning, the identical stack comes straight back.
bnd's biz.aQute.launcher.Launcher.createFramework() builds the framework
config as Properties p = new Properties(); p.putAll(properties) from the
launcher.properties baked into the executable jar, and never copies
System.getProperties(). -Dfelix.cm.dir does work, because that is a
bundle property read through BundleContext.getProperty, which falls back to
System.getProperty. Framework launching properties are a different read
path. Verify the fix landed with:
unzip -p openems-edge.jar launcher.properties | grep bootdelegation
— which is checkable without even starting the edge.
We are happy to supply logs, our reproduction, or to test a patch.
##Operating System
Docker on Windows 11, Eclipse Temurin 21, Apache Felix
Reproduction
- Run
JSONServer on a JDK ≥ 11 with SAAJ absent from the runtime classpath
(an OSGi container, or plain java -cp without javax.xml.soap-api).
- Connect any OCPP-J 1.6 charge point and let the server send one request
(ChangeAvailability from an equivalent of OpenEMS's sendInitialRequests
is enough).
- Observe
NoClassDefFoundError: javax/xml/soap/SOAPMessage and, immediately
after, that TCP connects to the listener port are refused.
Expected: a JSON-only deployment never needs SAAJ, and a failure in a trace
log path never takes down the listener.
Observed: first session kills the server.
Downstream Impact / Cross-reference:
This issue was identified during integration with OpenEMS: OpenEMS/openems#3896
Affected versions: v1.1 AND v1.2. Fixed in v2.0 (released
2025-12-12), where
Communicator.javacarries no SOAP reference at all.Verified by reading
Communicator.javaat each tag on 2026-08-20 — not inferredfrom release notes:
import javax.xml.soap.SOAPMessageinstanceof SOAPMessageinsendCall()sendCall()rewritten around theRadiointerfaceAlso present on the v1.1 tag in
ocpp-common/.../utilities/SugarUtil.java.Environment where we hit it: OpenEMS Edge 2026.8.0 (which pins this library
via
io.openems.wrapper/eu.chargetime.ocpp.bnd), Apache Felix, EclipseTemurin 21, Docker. OCPP-J 1.6 only — no SOAP transport configured or wanted.
Summary
Communicator.sendCall()executes aninstanceof SOAPMessagetest on everyoutgoing call, on both the JSON and the SOAP transports:
javax.xml.soap(SAAJ) was deprecated in Java 9 and removed from the JDK inJava 11 (JEP 320).
ocpp-common/pom.xml:55-58declaresjavax.xml.soap:javax.xml.soap-api:1.4.0at compile scope, so a plain Mavenbuild still works. Under OSGi, or under any deployment that does not put SAAJ on
the runtime classpath, resolving the
instanceofthrows.Two things make this much worse than a missing trace log:
touches SOAP still evaluates it, because
instanceofrequires the JVM toresolve
SOAPMessageregardless of the runtime type ofcall.logger.tracebeing disabled does not help — the branch is evaluated before the log call.
NoClassDefFoundErrorpropagates out of the java-websocket callback, thelistener dies, and port 8887 stops accepting. So the failure is not "one
dropped message" — the first charge point to send anything takes the whole
server down for every charge point.
Suggested fixes, cheapest first
SOAPCommunicatoralready exists in
ocpp-v1_6;ocpp-commonshould not name a SOAP type atall. This is what
mastereffectively did.ocpp-common, gate it without loading the class:if ("javax.xml.soap.SOAPMessage".equals(call.getClass().getName())), orClass.forNameguarded once and cached, or reflection inSugarUtil.try { … } catch (NoClassDefFoundError | LinkageError e) { logger.trace("Send a message: {}", call); }so a loggingconcern cannot kill a transport.
the dependency out of
provided/compile-only assumptions.What we are actually asking for
v2.0 already fixes this, so this is not a request for a fix — it is a question
about the 1.x line. Either answer is useful and we will act on it:
1.2.xpatch release carrying only the removal of this branch, forconsumers pinned to 1.x; or
please say so plainly — downstreams pinned at 1.1 (OpenEMS among them) need to
know the 1.x line will not receive it, so they can plan the major bump.
We are not asking you to backport the rest of master. If (b), we will carry the
request downstream ourselves — see "Why this still matters to OpenEMS" below.
Related: this was predicted here in 2019
#94 — "Refactor downcast of message types in higher level components"
(closed,
enhancement+good first issue) named this exact anti-pattern andits reason: the downcast of message types couples higher-level components to the
XML/SOAP libraries, and SOAP support had by then moved into the
ocpp-v1_6subproject. This report is that observation arriving as a production failure
seven years later — the JSON path still executing a SOAP downcast, on a JDK
where the class no longer ships.
Not a duplicate: #94 is a design-cleanliness enhancement with no crash, no
NoClassDefFoundError, and no version boundary. It is the argument for why the1.x line still deserves the removal.
Also checked and not duplicates: #19 (
SOAPSyncHelper.java issue— aforwardMessage()/promisesordering bug in the SOAP transport) and #137(
NoClassDefFoundError: eu/chargetime/ocpp/wss/WssSocketBuilder— a user whoomitted
OCPP-J-*.jarfrom their classpath, self-resolved). Same exception typein #137, entirely different cause: a library class missing because a jar was
left out, versus a JDK class the library references but does not declare.
Why this still matters to OpenEMS — verified on
develop, 2026-08-20This is not only a problem for our pinned tag. On OpenEMS's current default
branch:
So the pin is still 1.1.0 and the import is still missing — the defect is live
on
develop, not merely on the release we happened to build.And OpenEMS can fix its half in one line.
io.openems.edge.application/EdgeApp.bndrunalready uses-runproperties, for a different property:Adding
org.osgi.framework.bootdelegation=javax.xml.soapto that list is thewhole of the framework-side fix, in a file where the mechanism is already in
use — no new machinery, no new build step. (The SAAJ jar must still be supplied;
see the workaround below for why both halves are needed.) We will raise this
separately on the OpenEMS tracker and cross-link it here.
Workaround we ship (for anyone else hitting this)
Two halves, and both are required. Supplying the class is not enough on
OSGi, because the wrapper bundle's explicit
Import-Packagedoes not listjavax.xml.soap, so normal OSGi resolution will never wire it:javax.xml.soap-api-1.4.0.jarto the image and putit on the boot classloader via
-Xbootclasspath/a:— that is what Felix'sdefault
org.osgi.framework.bundle.parent=bootdelegates to.org.osgi.framework.bootdelegation=javax.xml.soap, which bypassesImport-Packageresolution entirely.We are happy to supply logs, our reproduction, or to test a patch.
##Operating System
Docker on Windows 11, Eclipse Temurin 21, Apache Felix
Reproduction
JSONServeron a JDK ≥ 11 with SAAJ absent from the runtime classpath(an OSGi container, or plain
java -cpwithoutjavax.xml.soap-api).(
ChangeAvailabilityfrom an equivalent of OpenEMS'ssendInitialRequestsis enough).
NoClassDefFoundError: javax/xml/soap/SOAPMessageand, immediatelyafter, that TCP connects to the listener port are refused.
Expected: a JSON-only deployment never needs SAAJ, and a failure in a trace
log path never takes down the listener.
Observed: first session kills the server.
Downstream Impact / Cross-reference:
This issue was identified during integration with OpenEMS: OpenEMS/openems#3896