Jakarta XML Binding API that automates the mapping between XML documents and Java objects through data binding
—
Jakarta XML Binding provides support for optimized binary data handling through MTOM (Message Transmission Optimization Mechanism) and SwA (SOAP with Attachments). This framework enables efficient processing of large binary data by avoiding base64 encoding overhead and supporting external attachment references.
The AttachmentMarshaller enables optimized binary data marshalling by creating external attachments instead of inline base64 encoding.
public abstract class AttachmentMarshaller {
// MTOM attachment support
public abstract String addMtomAttachment(
javax.activation.DataHandler data,
String elementNamespace,
String elementLocalName
);
public abstract String addMtomAttachment(
byte[] data,
int offset,
int length,
String mimeType,
String elementNamespace,
String elementLocalName
);
// SwA attachment support
public abstract String addSwaRefAttachment(javax.activation.DataHandler data);
// XOP package detection
public boolean isXOPPackage();
}Usage Examples:
// Custom AttachmentMarshaller implementation
public class FileAttachmentMarshaller extends AttachmentMarshaller {
private final File attachmentDir;
private final Map<String, String> attachmentMap = new HashMap<>();
private int attachmentCounter = 0;
public FileAttachmentMarshaller(File attachmentDir) {
this.attachmentDir = attachmentDir;
attachmentDir.mkdirs();
}
@Override
public String addMtomAttachment(DataHandler data, String elementNamespace, String elementLocalName) {
try {
String filename = "attachment_" + (++attachmentCounter) + ".dat";
File attachmentFile = new File(attachmentDir, filename);
// Write attachment data to file
try (InputStream is = data.getInputStream();
FileOutputStream fos = new FileOutputStream(attachmentFile)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
String cid = "cid:" + filename;
attachmentMap.put(cid, attachmentFile.getAbsolutePath());
System.out.printf("Created MTOM attachment: %s for element {%s}%s%n",
cid, elementNamespace, elementLocalName);
return cid;
} catch (IOException e) {
throw new RuntimeException("Failed to create attachment", e);
}
}
@Override
public String addMtomAttachment(byte[] data, int offset, int length, String mimeType,
String elementNamespace, String elementLocalName) {
try {
String filename = "attachment_" + (++attachmentCounter) +
getExtensionForMimeType(mimeType);
File attachmentFile = new File(attachmentDir, filename);
// Write binary data to file
try (FileOutputStream fos = new FileOutputStream(attachmentFile)) {
fos.write(data, offset, length);
}
String cid = "cid:" + filename;
attachmentMap.put(cid, attachmentFile.getAbsolutePath());
System.out.printf("Created MTOM attachment: %s (%s) for element {%s}%s%n",
cid, mimeType, elementNamespace, elementLocalName);
return cid;
} catch (IOException e) {
throw new RuntimeException("Failed to create attachment", e);
}
}
@Override
public String addSwaRefAttachment(DataHandler data) {
try {
String filename = "swa_" + (++attachmentCounter) + ".dat";
File attachmentFile = new File(attachmentDir, filename);
try (InputStream is = data.getInputStream();
FileOutputStream fos = new FileOutputStream(attachmentFile)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
String cid = "cid:" + filename;
attachmentMap.put(cid, attachmentFile.getAbsolutePath());
System.out.printf("Created SwA attachment: %s%n", cid);
return cid;
} catch (IOException e) {
throw new RuntimeException("Failed to create SwA attachment", e);
}
}
@Override
public boolean isXOPPackage() {
return true; // This marshaller creates XOP packages
}
private String getExtensionForMimeType(String mimeType) {
switch (mimeType.toLowerCase()) {
case "image/jpeg": return ".jpg";
case "image/png": return ".png";
case "image/gif": return ".gif";
case "application/pdf": return ".pdf";
case "text/plain": return ".txt";
default: return ".dat";
}
}
public Map<String, String> getAttachmentMap() {
return new HashMap<>(attachmentMap);
}
}
// Usage with marshaller
JAXBContext context = JAXBContext.newInstance(Document.class);
Marshaller marshaller = context.createMarshaller();
File attachmentDir = new File("attachments");
FileAttachmentMarshaller attachmentMarshaller = new FileAttachmentMarshaller(attachmentDir);
marshaller.setAttachmentMarshaller(attachmentMarshaller);
Document document = new Document();
document.setContent("Document content");
document.setImage(new DataHandler(new FileDataSource("large-image.jpg")));
marshaller.marshal(document, new File("document.xml"));
// Review created attachments
Map<String, String> attachments = attachmentMarshaller.getAttachmentMap();
System.out.println("Created attachments: " + attachments);The AttachmentUnmarshaller enables processing of optimized binary data during unmarshalling by resolving attachment references.
public abstract class AttachmentUnmarshaller {
// Retrieve attachment as DataHandler
public abstract javax.activation.DataHandler getAttachmentAsDataHandler(String cid);
// Retrieve attachment as byte array
public abstract byte[] getAttachmentAsByteArray(String cid);
// XOP package detection
public boolean isXOPPackage();
}Usage Examples:
// Custom AttachmentUnmarshaller implementation
public class FileAttachmentUnmarshaller extends AttachmentUnmarshaller {
private final File attachmentDir;
private final Map<String, String> attachmentMap;
public FileAttachmentUnmarshaller(File attachmentDir, Map<String, String> attachmentMap) {
this.attachmentDir = attachmentDir;
this.attachmentMap = new HashMap<>(attachmentMap);
}
@Override
public DataHandler getAttachmentAsDataHandler(String cid) {
String filePath = attachmentMap.get(cid);
if (filePath == null) {
throw new IllegalArgumentException("Attachment not found: " + cid);
}
File attachmentFile = new File(filePath);
if (!attachmentFile.exists()) {
throw new IllegalArgumentException("Attachment file not found: " + filePath);
}
System.out.printf("Retrieving attachment as DataHandler: %s -> %s%n", cid, filePath);
return new DataHandler(new FileDataSource(attachmentFile));
}
@Override
public byte[] getAttachmentAsByteArray(String cid) {
String filePath = attachmentMap.get(cid);
if (filePath == null) {
throw new IllegalArgumentException("Attachment not found: " + cid);
}
File attachmentFile = new File(filePath);
if (!attachmentFile.exists()) {
throw new IllegalArgumentException("Attachment file not found: " + filePath);
}
try {
System.out.printf("Retrieving attachment as byte array: %s -> %s%n", cid, filePath);
return Files.readAllBytes(attachmentFile.toPath());
} catch (IOException e) {
throw new RuntimeException("Failed to read attachment: " + filePath, e);
}
}
@Override
public boolean isXOPPackage() {
return true; // This unmarshaller handles XOP packages
}
}
// Usage with unmarshaller
JAXBContext context = JAXBContext.newInstance(Document.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
// Set up attachment unmarshaller with attachment map from marshalling
File attachmentDir = new File("attachments");
Map<String, String> attachmentMap = loadAttachmentMap(); // Load from previous marshalling
FileAttachmentUnmarshaller attachmentUnmarshaller =
new FileAttachmentUnmarshaller(attachmentDir, attachmentMap);
unmarshaller.setAttachmentUnmarshaller(attachmentUnmarshaller);
Document document = (Document) unmarshaller.unmarshal(new File("document.xml"));
// Access binary data
DataHandler imageData = document.getImage();
if (imageData != null) {
try (InputStream is = imageData.getInputStream();
FileOutputStream fos = new FileOutputStream("restored-image.jpg")) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
}Jakarta XML Binding provides annotations for controlling binary attachment behavior.
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface XmlAttachmentRef {
}
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE})
@Retention(RetentionPolicy.RUNTIME)
public @interface XmlInlineBinaryData {
}
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface XmlMimeType {
String value();
}Annotation Usage Examples:
@XmlRootElement
public class Document {
@XmlElement
private String title;
// Binary data as attachment reference
@XmlAttachmentRef
@XmlMimeType("image/jpeg")
private DataHandler profileImage;
// Force inline encoding (disable optimization)
@XmlInlineBinaryData
private byte[] signature;
// Large document with MIME type specification
@XmlAttachmentRef
@XmlMimeType("application/pdf")
private DataHandler documentPdf;
// Small binary data (will be inlined)
@XmlElement
private byte[] checksum;
// Constructors, getters, setters...
public Document() {}
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public DataHandler getProfileImage() { return profileImage; }
public void setProfileImage(DataHandler profileImage) { this.profileImage = profileImage; }
public byte[] getSignature() { return signature; }
public void setSignature(byte[] signature) { this.signature = signature; }
public DataHandler getDocumentPdf() { return documentPdf; }
public void setDocumentPdf(DataHandler documentPdf) { this.documentPdf = documentPdf; }
public byte[] getChecksum() { return checksum; }
public void setChecksum(byte[] checksum) { this.checksum = checksum; }
}MTOM (Message Transmission Optimization Mechanism) provides efficient binary data transmission by avoiding base64 encoding.
Complete MTOM Example:
public class MTOMExample {
public static void demonstrateMTOM() throws Exception {
// Create document with large binary data
Document document = new Document();
document.setTitle("Sample Document");
// Add large image as attachment
File imageFile = new File("large-photo.jpg");
DataHandler imageHandler = new DataHandler(new FileDataSource(imageFile));
document.setProfileImage(imageHandler);
// Add PDF document as attachment
File pdfFile = new File("specifications.pdf");
DataHandler pdfHandler = new DataHandler(new FileDataSource(pdfFile));
document.setDocumentPdf(pdfHandler);
// Add small signature (will be inlined)
document.setSignature("SIGNATURE".getBytes("UTF-8"));
// Add checksum (small, no optimization)
document.setChecksum(calculateChecksum(document));
// Marshall with MTOM support
JAXBContext context = JAXBContext.newInstance(Document.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
File attachmentDir = new File("mtom-attachments");
FileAttachmentMarshaller attachmentMarshaller =
new FileAttachmentMarshaller(attachmentDir);
marshaller.setAttachmentMarshaller(attachmentMarshaller);
File xmlOutput = new File("document-mtom.xml");
marshaller.marshal(document, xmlOutput);
System.out.println("Marshalled document with MTOM attachments");
System.out.println("XML file: " + xmlOutput.getAbsolutePath());
System.out.println("Attachments: " + attachmentMarshaller.getAttachmentMap());
// Unmarshall with MTOM support
Unmarshaller unmarshaller = context.createUnmarshaller();
FileAttachmentUnmarshaller attachmentUnmarshaller =
new FileAttachmentUnmarshaller(attachmentDir, attachmentMarshaller.getAttachmentMap());
unmarshaller.setAttachmentUnmarshaller(attachmentUnmarshaller);
Document restoredDocument = (Document) unmarshaller.unmarshal(xmlOutput);
System.out.println("Unmarshalled document: " + restoredDocument.getTitle());
// Verify binary data integrity
verifyBinaryData(restoredDocument, imageFile, pdfFile);
}
private static byte[] calculateChecksum(Document document) {
// Simple checksum calculation
return document.getTitle().getBytes().length > 0 ?
new byte[]{0x12, 0x34, 0x56, 0x78} :
new byte[]{0x00, 0x00, 0x00, 0x00};
}
private static void verifyBinaryData(Document document, File originalImage, File originalPdf)
throws Exception {
// Verify image data
if (document.getProfileImage() != null) {
try (InputStream original = new FileInputStream(originalImage);
InputStream restored = document.getProfileImage().getInputStream()) {
if (streamsEqual(original, restored)) {
System.out.println("✓ Image data integrity verified");
} else {
System.out.println("✗ Image data integrity check failed");
}
}
}
// Verify PDF data
if (document.getDocumentPdf() != null) {
try (InputStream original = new FileInputStream(originalPdf);
InputStream restored = document.getDocumentPdf().getInputStream()) {
if (streamsEqual(original, restored)) {
System.out.println("✓ PDF data integrity verified");
} else {
System.out.println("✗ PDF data integrity check failed");
}
}
}
// Verify signature (inlined data)
if (document.getSignature() != null) {
String signature = new String(document.getSignature(), "UTF-8");
if ("SIGNATURE".equals(signature)) {
System.out.println("✓ Signature data integrity verified");
} else {
System.out.println("✗ Signature data integrity check failed");
}
}
}
private static boolean streamsEqual(InputStream is1, InputStream is2) throws IOException {
byte[] buffer1 = new byte[8192];
byte[] buffer2 = new byte[8192];
int bytesRead1, bytesRead2;
while ((bytesRead1 = is1.read(buffer1)) != -1) {
bytesRead2 = is2.read(buffer2);
if (bytesRead1 != bytesRead2) {
return false;
}
for (int i = 0; i < bytesRead1; i++) {
if (buffer1[i] != buffer2[i]) {
return false;
}
}
}
return is2.read() == -1; // Ensure second stream is also at end
}
}MTOM and SwA are commonly used in web service scenarios for efficient binary data transmission.
// JAX-WS web service with MTOM support
@WebService
@MTOM(enabled = true)
public class DocumentService {
@WebMethod
public Document uploadDocument(
@WebParam(name = "title") String title,
@WebParam(name = "content") DataHandler content
) {
Document document = new Document();
document.setTitle(title);
document.setDocumentPdf(content);
// Process document...
return document;
}
@WebMethod
public DataHandler downloadDocument(
@WebParam(name = "documentId") String documentId
) {
// Retrieve document...
File documentFile = new File("documents/" + documentId + ".pdf");
return new DataHandler(new FileDataSource(documentFile));
}
}
// Client-side MTOM usage
@WebServiceClient
public class DocumentServiceClient {
public void uploadLargeDocument() throws Exception {
DocumentService service = new DocumentService();
DocumentServicePortType port = service.getDocumentServicePort();
// Enable MTOM on client side
BindingProvider bindingProvider = (BindingProvider) port;
bindingProvider.getRequestContext().put(
JAXWSProperties.MTOM_THRESOLD_VALUE,
1024 // Use MTOM for attachments > 1KB
);
// Upload large document
File largeFile = new File("specifications.pdf");
DataHandler fileHandler = new DataHandler(new FileDataSource(largeFile));
Document result = port.uploadDocument("Specifications", fileHandler);
System.out.println("Uploaded document: " + result.getTitle());
// Download document
DataHandler downloadedContent = port.downloadDocument(result.getId());
// Save downloaded content
try (InputStream is = downloadedContent.getInputStream();
FileOutputStream fos = new FileOutputStream("downloaded-specs.pdf")) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
}
}Binary attachment optimization provides significant performance benefits for large data:
// Performance comparison utility
public class AttachmentPerformanceComparison {
public static void compareMethods(File binaryFile) throws Exception {
byte[] fileData = Files.readAllBytes(binaryFile.toPath());
// Method 1: Inline base64 encoding (traditional)
long startTime = System.currentTimeMillis();
Document docInline = new Document();
docInline.setTitle("Inline Document");
docInline.setSignature(fileData); // Will be base64 encoded
JAXBContext context = JAXBContext.newInstance(Document.class);
Marshaller marshallerInline = context.createMarshaller();
StringWriter inlineWriter = new StringWriter();
marshallerInline.marshal(docInline, inlineWriter);
String inlineXml = inlineWriter.toString();
long inlineTime = System.currentTimeMillis() - startTime;
long inlineSize = inlineXml.getBytes("UTF-8").length;
// Method 2: MTOM attachment (optimized)
startTime = System.currentTimeMillis();
Document docMtom = new Document();
docMtom.setTitle("MTOM Document");
docMtom.setProfileImage(new DataHandler(new FileDataSource(binaryFile)));
Marshaller marshallerMtom = context.createMarshaller();
FileAttachmentMarshaller attachmentMarshaller =
new FileAttachmentMarshaller(new File("temp-attachments"));
marshallerMtom.setAttachmentMarshaller(attachmentMarshaller);
StringWriter mtomWriter = new StringWriter();
marshallerMtom.marshal(docMtom, mtomWriter);
String mtomXml = mtomWriter.toString();
long mtomTime = System.currentTimeMillis() - startTime;
long mtomSize = mtomXml.getBytes("UTF-8").length;
// Report results
System.out.printf("File size: %d bytes%n", fileData.length);
System.out.printf("Inline method: %d ms, XML size: %d bytes%n", inlineTime, inlineSize);
System.out.printf("MTOM method: %d ms, XML size: %d bytes%n", mtomTime, mtomSize);
System.out.printf("Size reduction: %.1f%%%n",
100.0 * (inlineSize - mtomSize) / inlineSize);
System.out.printf("Time improvement: %.1f%%%n",
100.0 * (inlineTime - mtomTime) / inlineTime);
}
}Binary attachments are particularly beneficial when:
Install with Tessl CLI
npx tessl i tessl/maven-jakarta-xml-bind--jakarta-xml-bind-api