← Back to list

SOAP in GO: Beyond the Happy Path

Because integrating SOAP isn’t always just generate and go.

Alex Kimeu · 2026-02-03 08:27 · 3 claps · 3.4 min read
#go #soap #wsdl #integration
Open on Medium ↗

SOAP in GO: Beyond the Happy Path

Because integrating SOAP isn’t always just generate and go.

Introduction

I recently worked on a project that required integrating a SOAP service using Go. I’d done this before, successfully and without drama. Generate stubs from a WSDL, wire up a client, call the methods, done. Clean. Predictable. Almost relaxing.

This time, though, SOAP decided to remind me why it still has a reputation.

The challenge wasn’t SOAP itself. It wasn’t Go either. It was a WSDL that looked innocent but quietly referenced other WSDLs and XSDs. That’s where things stopped being straightforward.

This article walks through:

  • How SOAP integration usually works in Go
  • Why WSDLs that reference other WSDLs are tricky
  • How this compares to Java’s SOAP ecosystem
  • What I ended up doing to make it work
  • Lessons learned if you ever find yourself here

A Quick SOAP Refresher

SOAP is XML-based, contract-first, and heavily schema-driven. The contract lives in a WSDL (Web Services Description Language), which defines:

  • Operations
  • Request/response structures
  • Bindings endpoints

In practice, integration usually looks like this:

WSDL → generate stubs → call methods → parse response

When that flow holds, life is good.

A SOAP Integration That Went Smoothly

Before this project, I had integrated another SOAP service in Go that was refreshingly simple.

  • Single WSDL
  • No imports
  • No external schemas
  • Everything self-contained

The workflow was exactly what you’d expect.

Step 1: Generate stubs

go install github.com/hooklift/gowsdl/cmd/gowsdl@latest
gowsdl simple_service.wsdl

Step 2: Use the generated client

client := NewSimpleServicePortType(
    "https://soap.example.com/simple",
    http.DefaultClient,
)

resp, err := client.GetAccount(ctx, &GetAccountRequest{
    AccountID: "12345",
})

That was it. No surprises. No archaeology.

This Time: The WSDL Had Opinions

This project’s WSDL looked fine at first glance:

<definitions ...>
  <types>
    <xsd:schema>
      <xsd:import namespace="http://example.com/common"
                  schemaLocation="common.xsd"/>
    </xsd:schema>
  </types>
</definitions>

Then common.xsd referenced another schema.

Which referenced another.

And somewhere down the chain, another WSDL.

The Real Structure Looked More Like This

main.wsdl
 ├── common.xsd
 │    ├── identifiers.xsd
 │    └── addresses.xsd
 └── other.wsdl
      └── other-types.xsd

This is perfectly valid SOAP. It’s also where Go tooling starts to struggle.

The Core Problem: Linked WSDLs and Schemas

Go can generate SOAP stubs from WSDLs. That part works.

The issue appears when:

  • WSDLs reference other WSDLs
  • Schemas are split across multiple files
  • Imports assume specific resolution paths
  • Relative URLs don’t resolve cleanly

Many Go SOAP generators:

  • Expect a mostly self-contained WSDL
  • Don’t recursively resolve imports well
  • Fail silently or generate incomplete structs

I couldn’t find a tool that handled this cleanly out of the box.

Why This Is Easier in Java

This is where Java quietly flexes.

With tools like **wsimport** (JAX-WS), Java:

  • Resolves linked WSDLs automatically
  • Follows schema imports recursively
  • Generates a complete, type-safe client
  • Handles SOAP headers and bindings gracefully
wsimport -keep -p com.example.soap main.wsdl

Five seconds later, you’re calling methods.

In Go, you earn it.

What I Ended Up Doing

Instead of fighting the tooling, I changed strategy.

Step 1: Treat SOAP as XML, Not Magic

In this project, I did not rely on a SOAP framework or a fully generated client to manage SOAP behavior. Instead, I leaned into Go’s strengths:

  • Explicit structs
  • Manual control
  • Clear data flow

I started by defining the SOAP envelope myself.

type Envelope struct {
    XMLName xml.Name `xml:"soapenv:Envelope"`
    SoapEnv string   `xml:"xmlns:soapenv,attr"`
    Body    Body
}

type Body struct {
    XMLName xml.Name `xml:"soapenv:Body"`
    Content any
}

Simple. Predictable. No surprises.

Step 2: Define the Contract Manually

From the WSDL, I extracted only what I actually needed and defined request structs explicitly:

type SampleRequest struct {
  XMLName xml.Name `xml:"getSampleData"`
  Username string `xml:"username"`
  Password string `xml:"password"`
  Code string `xml:"code"`
}

Step 3: Build a Thin SOAP Client

Instead of a massive generated client, I wrote a focused SOAP caller:

func callSOAP(endpoint string, payload any, resp any) error {
  envelope := Envelope{
    SoapEnv: "http://schemas.xmlsoap.org/soap/envelope/",
    Body: Body{Content: payload},
  }

  data, _ := xml.Marshal(envelope)

  req, _ := http.NewRequest("POST", endpoint, bytes.NewReader(data))
  req.Header.Set("Content-Type", "text/xml; charset=utf-8")

  res, err := http.DefaultClient.Do(req)
  if err != nil {
    return err
  }
  defer res.Body.Close()

  return xml.NewDecoder(res.Body).Decode(resp)
}

No frameworks. No surprises. Just XML in, XML out.

Handling Responses (and SOAP Faults)

SOAP loves wrapping everything in envelopes — including errors.

Instead of fighting that, I embraced it:

  • Read the raw XML on non-200 responses
  • Log it verbatim
  • Store it for debugging

This saved me hours later when authentication issues or schema mismatches cropped up.

Final Thoughts

SOAP is still around and it rewards precision over creativity.

When a service exposes a single, self-contained WSDL, integrating it in Go is fairly straightforward. You generate the stubs, call the methods, and move on. I’ve done this before, and it worked exactly as expected.

Things change when that WSDL starts referencing other WSDLs. At that point, Go tooling still gets the job done, but it makes you slow down and get more hands-on. Not because it can’t handle it, but because most tools assume simpler, standalone contracts.

So if you’re planning to integrate a SOAP service in Go, take a look at the WSDL first. If it pulls in other files, be prepared to spend some time understanding and shaping the integration yourself.

Happy coding!


메타데이터
post_id
dbc8b5cabe88
slug
soap-in-go-beyond-the-happy-path-dbc8b5cabe88
url
https://medium.com/@alekske/soap-in-go-beyond-the-happy-path-dbc8b5cabe88
canonical_url
https://medium.com/@alekske/soap-in-go-beyond-the-happy-path-dbc8b5cabe88
author_url
https://medium.com/@alekske
status
ok
fetched_at
2026-07-13 06:23:13