Convert Between JSON and SOAP XML
SOAP services exchange XML envelopes, but many HTTP clients use JSON. The body-transformer plugin can decode a JSON request, render a SOAP envelope, parse the XML response, and return a JSON document to the client.
This guide configures that two-way transformation with a sample country service.

Prerequisite(s)
- Install Docker.
- Install cURL to send requests to APISIX for validation.
- Install Git to download the sample SOAP service.
- Follow the Getting Started tutorial to start APISIX in Docker. The commands below use the
apisix-quickstart-netnetwork created by that tutorial. - If Admin API authentication is enabled, export an admin-role key as
ADMIN_API_KEY. The Docker quickstart disables authentication for local evaluation; keep it enabled in production.
Deploy an Example SOAP Service
Clone the Spring sample service and check out the revision tested in this guide:
git clone "https://github.com/spring-guides/gs-producing-web-service.git"
cd gs-producing-web-service/complete
git switch --detach c9c88ea61cfbcef0de6275c7b6c8b1320549017a
Run the service in the same Docker network as APISIX. The named volume caches Maven dependencies across container restarts:
docker volume create soap-maven-cache
docker rm -f quickstart-soap-service 2>/dev/null || true
docker run -d \
--rm \
--name quickstart-soap-service \
--network apisix-quickstart-net \
-v "$PWD:/workspace" \
-v soap-maven-cache:/root/.m2 \
-w /workspace \
eclipse-temurin:17-jdk@sha256:420933bf319e6aff4bec22eff8d2a31c824df5d853432e3dc0f46d6ea8dd5919 \
./mvnw spring-boot:run
Follow the startup logs:
docker logs -f quickstart-soap-service
Wait for Started ProducingWebServiceApplication, then press Ctrl+C. The application continues running in the background.
Create Transformation Templates
Create the request template. It reads name from the decoded JSON body and renders the SOAP request expected by the sample service:
cat > request-template.xml <<'EOF'
<?xml version="1.0"?>
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:Body>
<ns0:getCountryRequest xmlns:ns0="https://spring.io/guides/gs-producing-web-service">
<ns0:name>{{_escape_xml(name)}}</ns0:name>
</ns0:getCountryRequest>
</soap-env:Body>
</soap-env:Envelope>
EOF
Create the response template. The plugin removes XML namespace prefixes before exposing response fields to the template:
cat > response-template.json <<'EOF'
{% if Envelope.Body.Fault == nil then %}
{
"status":"{{_ctx.var.status}}",
"currency":"{{Envelope.Body.getCountryResponse.country.currency}}",
"population":{{Envelope.Body.getCountryResponse.country.population}},
"capital":"{{Envelope.Body.getCountryResponse.country.capital}}",
"name":"{{Envelope.Body.getCountryResponse.country.name}}"
}
{% else %}
{
"message":{*_escape_json(Envelope.Body.Fault.faultstring[1])*},
"code":"{{Envelope.Body.Fault.faultcode}}"
{% if Envelope.Body.Fault.faultactor ~= nil then %}
, "actor":"{{Envelope.Body.Fault.faultactor}}"
{% end %}
}
{% end %}
EOF
Encode the templates for safe insertion into the Admin API JSON document:
export REQUEST_TEMPLATE_BASE64="$(base64 < request-template.xml | tr -d '\r\n')"
export RESPONSE_TEMPLATE_BASE64="$(base64 < response-template.json | tr -d '\r\n')"
The templates use lua-resty-template syntax. The plugin also provides _escape_xml() and _escape_json() for context-appropriate escaping, _body for the original body, and _ctx for request context.
Configure APISIX
Create a route with the body-transformer plugin:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<EOF
{
"id": "body-transformer-route",
"methods": ["POST"],
"uri": "/ws",
"plugins": {
"body-transformer": {
"request": {
"template": "$REQUEST_TEMPLATE_BASE64",
"template_is_base64": true,
"input_format": "json"
},
"response": {
"template": "$RESPONSE_TEMPLATE_BASE64",
"template_is_base64": true,
"input_format": "xml"
}
},
"proxy-rewrite": {
"uri": "/services",
"headers": {
"set": {
"Content-Type": "text/xml"
}
}
},
"response-rewrite": {
"headers": {
"set": {
"Content-Type": "application/json"
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"quickstart-soap-service:8080": 1
}
}
}
EOF
❶ Decode the client request as JSON before rendering the request template.
❷ Decode the upstream response as XML before rendering the response template.
❸ Send the transformed request to the sample service's /services endpoint with a SOAP-compatible content type.
❹ Return the transformed response with a JSON content type.
❺ Resolve the sample service by its container name on apisix-quickstart-net.
Verify
Send a JSON request to the route:
curl "http://127.0.0.1:9080/ws" -X POST \
-H "Content-Type: application/json" \
-d '{"name":"Spain"}'
You should receive a response similar to the following:
{
"status": "200",
"currency": "EUR",
"population": 46704314,
"capital": "Madrid",
"name": "Spain"
}
The client sends and receives JSON. APISIX sends a SOAP XML request to the upstream service and transforms its XML response back to JSON.
Next Steps
The body-transformer plugin can also transform other supported input formats or modify request and response bodies. See the Plugin Hub reference for its configuration options.