Axis webservice client throws error on optional return element missing

The Axis webservice client throws “Didn’t find specified return QName” when a document/literal response omits an optional return element that is the operation’s only unbound output.

  • Lucee 7.0.2.106
  • Extension: Axis 1 Webservices for Jakarta EE (Lucee 7+) v1.5.0.6
  • Windows Server, Java 11
  • createObject("webservice", wsdlUrl) against a document/literal wrapped service

A document/literal wrapped webservice call fails when the SOAP response legally omits an optional return element (minOccurs="0"), but only when that element is the operation’s single unbound output. The same call succeeds the moment the element is present, or whenever a second response element is left unbound.

When it fails, the client throws:

Didn't find specified return QName {http://x}>OpResponse>Result!

Trigger (both conditions required):

  1. The response wrapper’s return element is optional (minOccurs="0") and the actual SOAP response omits it — which is valid per the schema.
  2. The request wrapper shares the element name(s) of every other child of the response wrapper, so Axis binds those as input parameters and the optional return element is left as the lone output. Axis then records it as a mandatory return QName and throws when it is absent.

What works (scope):

  • Returning the element (response includes <Result>) → works, value populated.
  • Leaving two or more response elements unbound by the request → works, the missing element simply comes back null.
  • Changing the request so it does not share the sibling name → works.

Some WSDLs (e.g. InterFAX FaxStatus) reuse the same element names in the request and response wrappers, and omit the optional result payload under certain conditions (e.g. when rate-limited). The result is an intermittent failure.

<cfscript>
	ns = "http://x";
	selfUrl = "http://" & cgi.http_host & cgi.script_name;

	// role 1: SOAP response — omits the optional <Result> by default
	if (cgi.request_method == "POST") {
		inner = (server.keyExists("omitReturn") && server.omitReturn) ? "" : "<Result>hi</Result>";
		cfcontent(type="text/xml; charset=utf-8", reset="true");
		writeOutput('<?xml version="1.0" encoding="utf-8"?>'
			& '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'
			& '<soap:Body><OpResponse xmlns="' & ns & '">' & inner & '<Code>0</Code></OpResponse></soap:Body></soap:Envelope>');
		abort;
	}

	// role 2: WSDL — response = optional return (Result) + one sibling (Code);
	//         request shares that one sibling name (Code), leaving Result as the lone output
	if (structKeyExists(url, "wsdl")) {
		cfcontent(type="text/xml; charset=utf-8", reset="true");
		writeOutput('<?xml version="1.0" encoding="utf-8"?>'
			& '<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:tns="' & ns & '" targetNamespace="' & ns & '">'
			& '<wsdl:types><s:schema elementFormDefault="qualified" targetNamespace="' & ns & '">'
			& '<s:element name="Op"><s:complexType><s:sequence>'
			& '<s:element minOccurs="1" maxOccurs="1" name="Code" type="s:int"/>'
			& '</s:sequence></s:complexType></s:element>'
			& '<s:element name="OpResponse"><s:complexType><s:sequence>'
			& '<s:element minOccurs="0" maxOccurs="1" name="Result" type="s:string"/>'
			& '<s:element minOccurs="1" maxOccurs="1" name="Code" type="s:int"/>'
			& '</s:sequence></s:complexType></s:element>'
			& '</s:schema></wsdl:types>'
			& '<wsdl:message name="OpIn"><wsdl:part name="parameters" element="tns:Op"/></wsdl:message>'
			& '<wsdl:message name="OpOut"><wsdl:part name="parameters" element="tns:OpResponse"/></wsdl:message>'
			& '<wsdl:portType name="P"><wsdl:operation name="Op"><wsdl:input message="tns:OpIn"/><wsdl:output message="tns:OpOut"/></wsdl:operation></wsdl:portType>'
			& '<wsdl:binding name="B" type="tns:P"><soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>'
			& '<wsdl:operation name="Op"><soap:operation soapAction="' & ns & '/Op" style="document"/>'
			& '<wsdl:input><soap:body use="literal"/></wsdl:input><wsdl:output><soap:body use="literal"/></wsdl:output>'
			& '</wsdl:operation></wsdl:binding>'
			& '<wsdl:service name="S"><wsdl:port name="P" binding="tns:B"><soap:address location="' & selfUrl & '"/></wsdl:port></wsdl:service>'
			& '</wsdl:definitions>');
		abort;
	}

	// role 3: caller
	server.omitReturn = (structKeyExists(url, "omit") ? val(url.omit) : 1) ? true : false;
	wsdlUrl = selfUrl & "?wsdl=1&v=" & getTickCount();
	cfcontent(type="text/plain", reset="true");
	try {
		ws = createObject("webservice", wsdlUrl);
		r = ws.Op(1);
		writeOutput("PASS (omitReturn=" & server.omitReturn & ") result=" & serializeJSON(r));
	} catch (any e) {
		writeOutput("FAIL (omitReturn=" & server.omitReturn & ") " & e.message);
	}
</cfscript>

Actual:

  • No parameters → FAIL (omitReturn=true) Didn't find specified return QName {http://x}>OpResponse>Result!
  • ?omit=0PASS (omitReturn=false) result={">Op>Code":"hi",">OpResponse>Result":null}

Expected: since Result is declared minOccurs="0", an absent Result should deserialize to null (as it already does in the working cases), not throw.