JAX-WS attachment with MTOM
A complete JAX-WS SOAP-based example to show how to use Message Transmission Optimization Mechanism (MTOM) and XML-Binary Optimized Packaging (XOP) technique to send a binary attachment (image) from server to client and vice verse.
There are ton of articles about what’s MTOM and the benefits of using it (see reference sites below), but it’s lack of complete example to use MTOM in JAX-WS, hope this example can fill in some missing pieces in the whole picture.
Enabling MTOM on server
Enable server to send attachment via MTOM is very easy, just annotate the web service implementation class with javax.xml.ws.soap.MTOM
.
1. WebService Endpoint
Here’s a RPC-style web service, published two methods, downloadImage(String name)
and uploadImage(Image data)
, to let user upload or download an image file.
File : ImageServer.java
package com.mkyong.ws;
import java.awt.Image;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
//Service Endpoint Interface
@WebService
@SOAPBinding(style = Style.RPC)
public interface ImageServer{
//download a image from server
@WebMethod Image downloadImage(String name);
//update image to server
@WebMethod String uploadImage(Image data);
}
File : ImageServerImpl.java
package com.mkyong.ws;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.jws.WebService;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.soap.MTOM;
//Service Implementation Bean
@MTOM
@WebService(endpointInterface = "com.mkyong.ws.ImageServer")
public class ImageServerImpl implements ImageServer{
@Override
public Image downloadImage(String name) {
try {
File image = new File("c:\\images\\" + name);
return ImageIO.read(image);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Override
public String uploadImage(Image data) {
if(data!=null){
//store somewhere
return "Upload Successful";
}
throw new WebServiceException("Upload Failed!");
}
}
File : ImagePublisher.java
package com.mkyong.endpoint;
import javax.xml.ws.Endpoint;
import com.mkyong.ws.ImageServerImpl;
//Endpoint publisher
public class ImagePublisher{
public static void main(String[] args) {
Endpoint.publish("http://localhost:9999/ws/image", new ImageServerImpl());
System.out.println("Server is published!");
}
}
2. WebService Client
Here’s a web service client, to access the published web service at URL “http://localhost:9999/ws/image“.
File : ImageClient.java
package com.mkyong.client;
import java.awt.Image;
import java.io.File;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;
import javax.xml.ws.soap.MTOMFeature;
import javax.xml.ws.soap.SOAPBinding;
import com.mkyong.ws.ImageServer;
public class ImageClient{
public static void main(String[] args) throws Exception {
URL url = new URL("http://localhost:9999/ws/image?wsdl");
QName qname = new QName("http://ws.mkyong.com/", "ImageServerImplService");
Service service = Service.create(url, qname);
ImageServer imageServer = service.getPort(ImageServer.class);
/************ test download ***************/
Image image = imageServer.downloadImage("rss.png");
//display it in frame
JFrame frame = new JFrame();
frame.setSize(300, 300);
JLabel label = new JLabel(new ImageIcon(image));
frame.add(label);
frame.setVisible(true);
System.out.println("imageServer.downloadImage() : Download Successful!");
}
}
3. HTTP and SOAP traffic
Here’s the HTTP and SOAP traffic generated by the client, captured by traffic monitoring tool.
P.S The first wsdl request is omitted to save space.
Client send request :
POST /ws/image HTTP/1.1
SOAPAction: ""
Accept: text/xml, multipart/related, text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Content-Type: text/xml; charset=utf-8
User-Agent: Java/1.6.0_13
Host: localhost:9999
Connection: keep-alive
Content-Length: 209
<?xml version="1.0" ?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:downloadImage xmlns:ns2="http://ws.mkyong.com/">
<arg0>rss.png</arg0>
</ns2:downloadImage>
</S:Body>
</S:Envelope>
Server send response :
HTTP/1.1 200 OK
Transfer-encoding: chunked
Content-type: multipart/related;
start="<rootpart*[email protected]>";
type="application/xop+xml";
boundary="uuid:c73c9ce8-6e02-40ce-9f68-064e18843428";
start-info="text/xml"
--uuid:c73c9ce8-6e02-40ce-9f68-064e18843428
Content-Id: <rootpart*[email protected]>
Content-Type: application/xop+xml;charset=utf-8;type="text/xml"
Content-Transfer-Encoding: binary
<?xml version="1.0" ?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:downloadImageResponse xmlns:ns2="http://ws.mkyong.com/">
<return>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include"
href="cid:[email protected]">
</xop:Include>
</return>
</ns2:downloadImageResponse>
</S:Body>
</S:Envelope>
--uuid:c73c9ce8-6e02-40ce-9f68-064e18843428
Content-Id: <[email protected]>
Content-Type: image/png
Content-Transfer-Encoding: binary
Binary data here.............
Enabling MTOM on client
Enable client to send attachment via MTOM to server is required some extra efforts, see following example :
//codes enable MTOM in client
BindingProvider bp = (BindingProvider) imageServer;
SOAPBinding binding = (SOAPBinding) bp.getBinding();
binding.setMTOMEnabled(true);
1. WebService Client
Here’s a webservice client to send an image file to published endpoint above (http://localhost:8888/ws/image) via MTOM.
File : ImageClient.java
package com.mkyong.client;
import java.awt.Image;
import java.io.File;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;
import javax.xml.ws.soap.MTOMFeature;
import javax.xml.ws.soap.SOAPBinding;
import com.mkyong.ws.ImageServer;
public class ImageClient{
public static void main(String[] args) throws Exception {
URL url = new URL("http://localhost:8888/ws/image?wsdl");
QName qname = new QName("http://ws.mkyong.com/", "ImageServerImplService");
Service service = Service.create(url, qname);
ImageServer imageServer = service.getPort(ImageServer.class);
/************ test upload ****************/
Image imgUpload = ImageIO.read(new File("c:\\images\\rss.png"));
//enable MTOM in client
BindingProvider bp = (BindingProvider) imageServer;
SOAPBinding binding = (SOAPBinding) bp.getBinding();
binding.setMTOMEnabled(true);
String status = imageServer.uploadImage(imgUpload);
System.out.println("imageServer.uploadImage() : " + status);
}
}
2. HTTP and SOAP traffic
Here’s the HTTP and SOAP traffic generated by the client, captured by traffic monitoring tool.
P.S The first wsdl request is omitted to save space.
Client send request :
POST /ws/image HTTP/1.1
Content-type: multipart/related;
start="<rootpart*[email protected]>";
type="application/xop+xml";
boundary="uuid:751f2e5d-47f8-47d8-baf0-f793c29bd931";
start-info="text/xml"
Soapaction: ""
Accept: text/xml, multipart/related, text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
User-Agent: JAX-WS RI 2.1.6 in JDK 6
Host: localhost:9999
Connection: keep-alive
Content-Length: 6016
--uuid:751f2e5d-47f8-47d8-baf0-f793c29bd931
Content-Id: <rootpart*[email protected]>
Content-Type: application/xop+xml;charset=utf-8;type="text/xml"
Content-Transfer-Encoding: binary
<?xml version="1.0" ?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:uploadImage xmlns:ns2="http://ws.mkyong.com/">
<arg0>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include"
href="cid:[email protected]">
</xop:Include>
</arg0>
</ns2:uploadImage>
</S:Body>
</S:Envelope>
--uuid:751f2e5d-47f8-47d8-baf0-f793c29bd931
Content-Id: <[email protected]>
Content-Type: image/png
Content-Transfer-Encoding: binary
Binary data here.............
Server send response :
HTTP/1.1 200 OK
Transfer-encoding: chunked
Content-type: multipart/related;
start="<rootpart*[email protected]>";
type="application/xop+xml";
boundary="uuid:188a5835-198b-4c28-9b36-bf030578f2bd";
start-info="text/xml"
--uuid:188a5835-198b-4c28-9b36-bf030578f2bd
Content-Id: <rootpart*[email protected]>
Content-Type: application/xop+xml;charset=utf-8;type="text/xml"
Content-Transfer-Encoding: binary
<?xml version="1.0" ?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:uploadImageResponse xmlns:ns2="http://ws.mkyong.com/">
<return>Upload Successful</return>
</ns2:uploadImageResponse>
</S:Body>
</S:Envelope>
--uuid:188a5835-198b-4c28-9b36-bf030578f2bd--
Complete WSDL document
For those who are interested to study the WSDL file, you can get the wsdl file via URL : http://localhost:9999/ws/image?wsdl
Sample of the ImageServer WSDL file
<?xml version="1.0" encoding="UTF-8"?>
<definitions
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://ws.mkyong.com/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="http://ws.mkyong.com/"
name="ImageServerImplService">
<types></types>
<message name="downloadImage">
<part name="arg0" type="xsd:string"></part>
</message>
<message name="downloadImageResponse">
<part name="return" type="xsd:base64Binary"></part>
</message>
<message name="uploadImage">
<part name="arg0" type="xsd:base64Binary"></part>
</message>
<message name="uploadImageResponse">
<part name="return" type="xsd:string"></part>
</message>
<portType name="ImageServer">
<operation name="downloadImage">
<input message="tns:downloadImage"></input>
<output message="tns:downloadImageResponse"></output>
</operation>
<operation name="uploadImage">
<input message="tns:uploadImage"></input>
<output message="tns:uploadImageResponse"></output>
</operation>
</portType>
<binding name="ImageServerImplPortBinding" type="tns:ImageServer">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc">
</soap:binding>
<operation name="downloadImage">
<soap:operation soapAction=""></soap:operation>
<input>
<soap:body use="literal" namespace="http://ws.mkyong.com/"></soap:body>
</input>
<output>
<soap:body use="literal" namespace="http://ws.mkyong.com/"></soap:body>
</output>
</operation>
<operation name="uploadImage">
<soap:operation soapAction=""></soap:operation>
<input>
<soap:body use="literal" namespace="http://ws.mkyong.com/">
</soap:body>
</input>
<output>
<soap:body use="literal" namespace="http://ws.mkyong.com/">
</soap:body>
</output>
</operation>
</binding>
<service name="ImageServerImplService">
<port name="ImageServerImplPort" binding="tns:ImageServerImplPortBinding">
<soap:address location="http://localhost:9999/ws/image"></soap:address>
</port>
</service>
</definitions>
Download Source Code
Reference
- http://www.devx.com/xml/Article/34797/1763/page/1
- http://download.oracle.com/docs/cd/E17802_01/webservices/webservices/docs/2.0/jaxws/mtom-swaref.html
- http://www.crosschecknet.com/intro_to_mtom.php
- http://download.oracle.com/docs/cd/E12840_01/wls/docs103/webserv_adv/mtom.html
- http://www.theserverside.com/news/1363957/Sending-Attachments-with-SOAP
- http://metro.java.net/guide/Binary_Attachments__MTOM_.html
dude, you are like stackoverflow but with tutorials
above syntax must be generated in WSDL that is absent in the given WSDL .
Hi ,
I ahev a doubt on why bindingprovider was not set to enable MTOM while calling downloadImage method at client side ?
But i see it was enabled while calling upload image at client side .
Thanks
Not Working.
package com.kb.ws;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.jws.WebMethod;
import javax.jws.WebService;
@WebService(endpointInterface = “com.kb.ws.ImageService”)
public class ImageServiceImpl implements ImageService {
@Override
@WebMethod
public Image downloadImage(String imageName) {
File image = new File(“C:\SOAPUI(don’t delete)\”
+ imageName);
try {
Image img = ImageIO.read(image);
return img;
} catch (IOException ie) {
ie.printStackTrace();
}
return null;
}
}
Please suggest.
Thanks a lot for nice article. But It is not working on Jboss Server. Could you help me?
Thanks, nice examples
Thanks for your article. Does you post some information about the possibility of get blob data from database in chunk?? Via InputStream, DataHandler and Mtom WS? Thanks
Thanks for your article. Does you post some information about the possibility of get mtom content from database in chunk?? Via InputStream and DataHandler objects? Thanks
Hi,
In the class ImageClient, what the function this code below for the test upload?
Because I can upload an image without the code snippet below and the upload works fine in the server.
//enable MTOM in client
//BindingProvider bp = (BindingProvider) imageServer;
//SOAPBinding binding = (SOAPBinding) bp.getBinding();
//binding.setMTOMEnabled(true);
and,
the size image was reduced automatically, original size was 1.7 MiB to 503 KiB. Reduce the size it’s automatically in MTOM?
I’m using this code for save the Image data in the upload method.
ImageIO.write((BufferedImage)data, “jpg”, outputfile);
thanks a lot for your post.
really helpful and working 🙂
great entry! helped a lot 🙂
May I know if this can be modified to upload image files but also pdf files?
Really helpful!
Thanks a lot.
Thanks mkyong,
Now I want to enable ‘fast infoset’ in above example for client and service side, What changes need to do ?
Thanks,
Niraj
Hi,
I have the following question: Is there any problem (conceptual or technical) with this approach if I want to work with Style.Document Webservices?
I am able to slim down without even working out by using frutaplanta robust edition .
you are great sir..
I like your articles which I use to learn among others about spring, web service security. Thank you.
Btw I put link to your article in my blog: http://soa-java.blogspot.nl/2012/09/mtom-email-attachment.html
Although the response has a binary attachment, there is no xop tag. Instead, there is the ascii base64 attachment inside a “return” tag and “Content-Type: text/xml; charset=utf-8”.
It works fine if I use soapUI and force MTOM, but I can’t make it with a dynamic proxy, although it returns true for SOAPBinding.isMTOMEnabled().
Hello,
I’m getting this error when doing the tuto in web app context:
INFO: Starting Coyote HTTP/1.1 on http-8080
28 févr. 2013 11:48:35 com.sun.xml.ws.transport.http.servlet.WSServletDelegate doGet
GRAVE: caught throwable
java.lang.NullPointerException
at com.sun.xml.stream.writers.XMLStreamWriterImpl.getProperty(XMLStreamWriterImpl.java:441)
at com.sun.xml.ws.util.xml.XMLStreamWriterFilter.getProperty(XMLStreamWriterFilter.java:139)
at com.sun.xml.ws.streaming.XMLStreamWriterUtil.getOutputStream(XMLStreamWriterUtil.java:77)
at com.sun.xml.ws.message.jaxb.JAXBMessage.writePayloadTo(JAXBMessage.java:313)
at com.sun.xml.ws.message.AbstractMessageImpl.writeTo(AbstractMessageImpl.java:142)
at com.sun.xml.ws.encoding.MtomCodec.encode(MtomCodec.java:157)
at com.sun.xml.ws.encoding.SOAPBindingCodec.encode(SOAPBindingCodec.java:258)
at com.sun.xml.ws.transport.http.HttpAdapter.encodePacket(HttpAdapter.java:320)
at com.sun.xml.ws.transport.http.HttpAdapter.access$100(HttpAdapter.java:93)
at com.sun.xml.ws.transport.http.HttpAdapter$HttpToolkit.handle(HttpAdapter.java:454)
at com.sun.xml.ws.transport.http.HttpAdapter.handle(HttpAdapter.java:244)
at com.sun.xml.ws.transport.http.servlet.ServletAdapter.handle(ServletAdapter.java:135)
at com.sun.xml.ws.transport.http.servlet.WSServletDelegate.doGet(WSServletDelegate.java:129)
at com.sun.xml.ws.transport.http.servlet.WSServletDelegate.doPost(WSServletDelegate.java:160)
at com.sun.xml.ws.transport.http.servlet.WSServlet.doPost(WSServlet.java:75)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:662)
28 févr. 2013 15:44:36 com.sun.xml.ws.transport.http.servlet.WSServletDelegate doGet
GRAVE: caught throwable
java.lang.NullPointerException
at com.sun.xml.stream.writers.XMLStreamWriterImpl.getProperty(XMLStreamWriterImpl.java:441)
at com.sun.xml.ws.util.xml.XMLStreamWriterFilter.getProperty(XMLStreamWriterFilter.java:139)
at com.sun.xml.ws.streaming.XMLStreamWriterUtil.getOutputStream(XMLStreamWriterUtil.java:77)
at com.sun.xml.ws.message.jaxb.JAXBMessage.writePayloadTo(JAXBMessage.java:313)
at com.sun.xml.ws.message.AbstractMessageImpl.writeTo(AbstractMessageImpl.java:142)
at com.sun.xml.ws.encoding.MtomCodec.encode(MtomCodec.java:157)
at com.sun.xml.ws.encoding.SOAPBindingCodec.encode(SOAPBindingCodec.java:258)
at com.sun.xml.ws.transport.http.HttpAdapter.encodePacket(HttpAdapter.java:320)
at com.sun.xml.ws.transport.http.HttpAdapter.access$100(HttpAdapter.java:93)
at com.sun.xml.ws.transport.http.HttpAdapter$HttpToolkit.handle(HttpAdapter.java:454)
at com.sun.xml.ws.transport.http.HttpAdapter.handle(HttpAdapter.java:244)
at com.sun.xml.ws.transport.http.servlet.ServletAdapter.handle(ServletAdapter.java:135)
at com.sun.xml.ws.transport.http.servlet.WSServletDelegate.doGet(WSServletDelegate.java:129)
at com.sun.xml.ws.transport.http.servlet.WSServletDelegate.doPost(WSServletDelegate.java:160)
at com.sun.xml.ws.transport.http.servlet.WSServlet.doPost(WSServlet.java:75)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:662)
Thank you for your articles.
It is very helpful …
Why does the example(upload) still work even though I removed annotation @MTOM?
This is some great stuff. Here’s what else I did with it.
I wrote an RPC web service. I wanted to use encryption but the algorithms introduce invalid XML characters. You couldn’t transfer the data due to XMLStreamException(s).
I used your example to transfer the encrypted data as byte[]. I was able to use the encryption!
Next I wrote up some code to do serialized Object transfers. I uploaded a Customer object, changed the name using setName(String s) and then sent the object back down. The name changed!
I have a question
public ImageServer getImageServerImplPort(WebServiceFeature… features)
What’s up with the WebServiceFeature(dot dot dot)? How does (dot dot dot) compile?
Thanks for another killer example.
In the case of upload, the client is using the java.awt.Image object and pass it to the remote method call uploadImage(). How does non-java consumers call this method when they dont have java.awt.Image library?
Thanks for your article. It is really helpful. I created my Mtom project successfully but I am facing with another problem about Mtom&soap signature. While trying to verify soap signature with SoapHandler, Soaphandler tries to keep all message and destroys Mtom optimization. Then it switches the attachment to base64 encoded. Do you know anything about this? How can i verify the soap message properly without destroy Mtom optimization?
Thanks
mkyoung,
Thank you for taking the time to put this tutorial together. You saved us many more hours of banging our heads against the wall trying to figure out how to correctly enable MTOM on the client to send a large file up to the server.
Thanks,
Paul
But I banging my head for hours to come out this lolx ~
Hi Mykong,
Whenever I am trying to create a web sevice which accept DataHandler, Image or PDF, on client side, it always mapped to byte[].
How to avoid this ??
Please reply
Thanks
Sonu Kumar
Image Upload and Download operations are working fine. But i would like to know how to do the same for any document and does this work with the document >4MB?
Please reply me.
Thanks & Regards
Uday
hi M mkyong
I want know if it is possible to use a php client (zend framework)
i hope that you understand me
forgive me for my english
Dear,
The main problem is webclient is another machine so it is unable to find ImageServer class.then how it will work.
I am new to SOAP webservice. I downloaded your code & used but i got an error at ” ImageServer imageServer = service.getPort(ImageServer.class);” saying nullpointerException. I am using Eclipse(helios) with tomcat 6 .I have no knowledge of xml too.