Send a SOAP Object with Feign Client – 用伪客户端发送一个SOAP对象

最后修改: 2022年 4月 1日

1. Overview


Feign abstracts the HTTP calls and makes them declarative. By doing so, Feign hides the lower-level details like HTTP connection management,  hardcoded-URLs, and other boilerplate code. The significant advantage of using Feign clients is that HTTP calls are made easy and eliminate a lot of code. Typically, we use the Feign for REST APIs application/json media type. However, the Feign clients work well with other media types like text/xml, multipart requests, etc.,

Feign对HTTP调用进行了抽象,使其成为声明性的。通过这样做,Feign隐藏了低级别的细节,如HTTP连接管理、硬编码URL和其他模板代码。使用Feign客户端的显著优势是,HTTP调用变得简单,并消除了大量的代码。通常情况下,我们使用Feign用于REST APIs application/json 媒体类型。然而,Feign客户端也能很好地处理其他媒体类型,如text/xml、多部分请求等。

In this tutorial, let’s learn how to invoke a SOAP-based web service (text/xml) using Feign.


2. SOAP Web Service


Let’s assume that there is a SOAP web service with two operations – getUser and createUser.

我们假设有一个SOAP Web服务,有两个操作–getUsercreateUser

Let’s use cURL to invoke the operation createUser:


curl -d @request.xml -i -o -X POST --header 'Content-Type: text/xml'

Here, the request.xml contains the SOAP payload:


<soapenv:Envelope xmlns:soapenv=""
                 <feig:name>john doe</feig:name>

If all the configurations are correct, we get a successful response:


<SOAP-ENV:Envelope xmlns:SOAP-ENV="">
        <ns2:createUserResponse xmlns:ns2="">
            <ns2:message>Success! Created the user with id - 1</ns2:message>

Similarly, the other operation, getUser can also be invoked using cURL.


3. Dependencies


Next, let’s see how to use Feign to invoke this SOAP web service. Let’s develop two different clients to invoke a SOAP service. Feign supports multiple existing HTTP Clients like Apache HttpComponents, OkHttp,, etc. Let’s use Apache HttpComponents as our underlying HTTP client. First, let’s add dependencies for OpenFeign Apache HttpComponents:

接下来,让我们看看如何使用Feign来调用这个SOAP网络服务。让我们开发两个不同的客户端来调用一个SOAP服务。Feign支持多种现有的HTTP客户端,如Apache等。让我们使用Apache HttpComponents 作为我们的底层HTTP客户端。首先,让我们为OpenFeign Apache HttpComponents添加依赖项。


In the next sections, let’s learn a couple of ways to invoke the SOAP web services using Feign.


4. SOAP Object as Plain-Text


We can send the SOAP request as plain text with content-type and accept headers set to text/xml. Let’s now develop a client that demonstrates this approach:


public interface SoapClient {
    @Headers({"SOAPAction: createUser", "Content-Type: text/xml;charset=UTF-8",
      "Accept: text/xml"})
    String createUserWithPlainText(String soapBody);

Here, createUserWithPlainText takes a String SOAP payload. Note that we defined the accept and content-type headers explicitly. This is because when sending a SOAP body as text, it is mandatory to mention the Content-Type and Accept headers as text/xml.

这里,createUserWithPlainText接收了一个StringSOAP有效载荷。请注意,我们明确定义了 accept content-type 头信息。这是因为当以文本形式发送SOAP主体时,必须提到Content-TypeAccept头信息为text/xml。文本/xml

One downside of this approach is we should know the SOAP payload beforehand. Fortunately, if the WSDL is available, the payload can be generated using open-source tools like SoapUI. Once the payload is ready, let’s invoke the SOAP web service using Feign:


void givenSOAPPayload_whenRequest_thenReturnSOAPResponse() throws Exception {
    String successMessage="Success! Created the user with id";
    SoapClient client = Feign.builder()
       .client(new ApacheHttp5Client())
       .target(SoapClient.class, "http://localhost:18080/ws/users/");
    assertDoesNotThrow(() -> client.createUserWithPlainText(soapPayload()));
    String soapResponse= client.createUserWithPlainText(soapPayload());

Feign supports logging of the SOAP messages and other HTTP-related information. This information is critical for debugging. So let’s enable the Feign logging. The logging of these messages requires an additional feign-slf4j dependency:



Let’s enhance our test case to include the logging information:


SoapClient client = Feign.builder()
  .client(new ApacheHttp5Client())
  .logger(new Slf4jLogger(SoapClient.class))
  .target(SoapClient.class, "http://localhost:18080/ws/users/");

Now, when we run the test, we have the logs similar to:


18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> "SOAPAction: createUser[\r][\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> "<soapenv:Envelope xmlns:soapenv=""
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <soapenv:Header/>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <soapenv:Body>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:createUserRequest>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:user>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:id>1</feig:id>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:name>john doe</feig:name>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:email></feig:email>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " </feig:user>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " </feig:createUserRequest>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " </soapenv:Body>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> "</soapenv:Envelope>"
18:01:58.300 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 << "<SOAP-ENV:Envelope xmlns:SOAP-ENV=""><SOAP-ENV:Header/><SOAP-ENV:Body><ns2:createUserResponse xmlns:ns2=""><ns2:message>Success! Created the user with id - 1</ns2:message></ns2:createUserResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>"

5. Feign SOAP Codec

5 佯装SOAP编解码器

A cleaner and better approach to invoke a SOAP Webservice is using Feign’s SOAP Codec. The codec helps marshal the SOAP messages (Java to SOAP)/unmarshalling (SOAP to Java). However, the codec requires an additional feign-soap dependency. Therefore, let’s declare this dependency:

使用Feign的SOAP编解码器来调用SOAP Webservice是一种更简洁、更好的方法。该编解码器有助于整理 SOAP 消息(Java 至 SOAP)/解除整理(SOAP 至 Java)。然而,该编解码器需要一个额外的feign-soap依赖项。因此,让我们声明这个依赖关系。


Feign SOAP codec encodes and decodes the SOAP objects using JAXB and SoapMessage and the JAXBContextFactory provides the required marshalers and unmarshalers.

Feign SOAP编解码器使用JAXBSoapMessage对SOAP对象进行编码和解码,JAXBContextFactory 提供了所需的marshalers和unmarshalers。

Next, based on the XSD that we created, let’s generate the domain classes. JAXB requires these domain classes to marshal and unmarshal the SOAP messages. First, let’s add a plugin to our pom.xml:



Here, we configured the plugin to run during the compile phase. Now, let’s generate the stubs:


mvn clean compile

After a successful build, the target folder contains the sources:


Folder Structure

Next, let’s use these stubs and Feign to invoke the SOAP web service. But, first, let’s add a new method to our SoapClient:


@Headers({"Content-Type: text/xml;charset=UTF-8"})
CreateUserResponse createUserWithSoap(CreateUserRequest soapBody);

Next, let’s test the SOAP web service:


void whenSoapRequest_thenReturnSoapResponse() {
    JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
    SoapClient client = Feign.builder()
      .encoder(new SOAPEncoder(jaxbFactory))
      .decoder(new SOAPDecoder(jaxbFactory))
      .target(SoapClient.class, "http://localhost:18080/ws/users/");
    CreateUserRequest request = new CreateUserRequest();
    User user = new User();
    user.setName("John Doe");
    CreateUserResponse response = client.createUserWithSoap(request);


Let’s enhance our test case to log the HTTP and SOAP messages:


SoapClient client = Feign.builder()
  .encoder(new SOAPEncoder(jaxbFactory))
  .errorDecoder(new SOAPErrorDecoder())
  .logger(new Slf4jLogger())
  .decoder(new SOAPDecoder(jaxbFactory))
  .target(SoapClient.class, "http://localhost:18080/ws/users/");

This code generates similar logs that we saw earlier.


Finally, let’s handle SOAP Faults. Feign provides a SOAPErrorDecoder that returns a SOAP Fault as SOAPFaultException. So, let’s set this SOAPErrorDecoder  as a Feign error decoder and handle the SOAP Faults:

最后,让我们来处理SOAP Faults。Feign提供了一个SOAPErrorDecoder,它将SOAP Fault作为SOAPFaultException返回。所以,让我们把这个SOAPErrorDecoder设为Feign错误解码器,并处理SOAP Faults。

SoapClient client = Feign.builder()
  .encoder(new SOAPEncoder(jaxbFactory))
  .errorDecoder(new SOAPErrorDecoder())  
  .decoder(new SOAPDecoder(jaxbFactory))
  .target(SoapClient.class, "http://localhost:18080/ws/users/");
try {
} catch (SOAPFaultException soapFaultException) {
    assertTrue(soapFaultException.getMessage().contains("This is a reserved user id"));

Here, if the SOAP web service throws a SOAP Fault, it will be handled by the SOAPFaultException.


6. Conclusion


In this article, we learned to invoke a SOAP web service using Feign. Feign is a declarative HTTP client that makes it easy to invoke SOAP/REST web services. The advantage of using Feign is it reduces the lines of code. Lesser lines of code lead to lesser bugs and lesser unit tests.


As always, the complete source code is available over on GitHub.