0
# Binary Attachments
1
2
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.
3
4
## Capabilities
5
6
### AttachmentMarshaller
7
8
The AttachmentMarshaller enables optimized binary data marshalling by creating external attachments instead of inline base64 encoding.
9
10
```java { .api }
11
public abstract class AttachmentMarshaller {
12
// MTOM attachment support
13
public abstract String addMtomAttachment(
14
javax.activation.DataHandler data,
15
String elementNamespace,
16
String elementLocalName
17
);
18
19
public abstract String addMtomAttachment(
20
byte[] data,
21
int offset,
22
int length,
23
String mimeType,
24
String elementNamespace,
25
String elementLocalName
26
);
27
28
// SwA attachment support
29
public abstract String addSwaRefAttachment(javax.activation.DataHandler data);
30
31
// XOP package detection
32
public boolean isXOPPackage();
33
}
34
```
35
36
**Usage Examples:**
37
38
```java
39
// Custom AttachmentMarshaller implementation
40
public class FileAttachmentMarshaller extends AttachmentMarshaller {
41
private final File attachmentDir;
42
private final Map<String, String> attachmentMap = new HashMap<>();
43
private int attachmentCounter = 0;
44
45
public FileAttachmentMarshaller(File attachmentDir) {
46
this.attachmentDir = attachmentDir;
47
attachmentDir.mkdirs();
48
}
49
50
@Override
51
public String addMtomAttachment(DataHandler data, String elementNamespace, String elementLocalName) {
52
try {
53
String filename = "attachment_" + (++attachmentCounter) + ".dat";
54
File attachmentFile = new File(attachmentDir, filename);
55
56
// Write attachment data to file
57
try (InputStream is = data.getInputStream();
58
FileOutputStream fos = new FileOutputStream(attachmentFile)) {
59
byte[] buffer = new byte[8192];
60
int bytesRead;
61
while ((bytesRead = is.read(buffer)) != -1) {
62
fos.write(buffer, 0, bytesRead);
63
}
64
}
65
66
String cid = "cid:" + filename;
67
attachmentMap.put(cid, attachmentFile.getAbsolutePath());
68
69
System.out.printf("Created MTOM attachment: %s for element {%s}%s%n",
70
cid, elementNamespace, elementLocalName);
71
72
return cid;
73
74
} catch (IOException e) {
75
throw new RuntimeException("Failed to create attachment", e);
76
}
77
}
78
79
@Override
80
public String addMtomAttachment(byte[] data, int offset, int length, String mimeType,
81
String elementNamespace, String elementLocalName) {
82
try {
83
String filename = "attachment_" + (++attachmentCounter) +
84
getExtensionForMimeType(mimeType);
85
File attachmentFile = new File(attachmentDir, filename);
86
87
// Write binary data to file
88
try (FileOutputStream fos = new FileOutputStream(attachmentFile)) {
89
fos.write(data, offset, length);
90
}
91
92
String cid = "cid:" + filename;
93
attachmentMap.put(cid, attachmentFile.getAbsolutePath());
94
95
System.out.printf("Created MTOM attachment: %s (%s) for element {%s}%s%n",
96
cid, mimeType, elementNamespace, elementLocalName);
97
98
return cid;
99
100
} catch (IOException e) {
101
throw new RuntimeException("Failed to create attachment", e);
102
}
103
}
104
105
@Override
106
public String addSwaRefAttachment(DataHandler data) {
107
try {
108
String filename = "swa_" + (++attachmentCounter) + ".dat";
109
File attachmentFile = new File(attachmentDir, filename);
110
111
try (InputStream is = data.getInputStream();
112
FileOutputStream fos = new FileOutputStream(attachmentFile)) {
113
byte[] buffer = new byte[8192];
114
int bytesRead;
115
while ((bytesRead = is.read(buffer)) != -1) {
116
fos.write(buffer, 0, bytesRead);
117
}
118
}
119
120
String cid = "cid:" + filename;
121
attachmentMap.put(cid, attachmentFile.getAbsolutePath());
122
123
System.out.printf("Created SwA attachment: %s%n", cid);
124
125
return cid;
126
127
} catch (IOException e) {
128
throw new RuntimeException("Failed to create SwA attachment", e);
129
}
130
}
131
132
@Override
133
public boolean isXOPPackage() {
134
return true; // This marshaller creates XOP packages
135
}
136
137
private String getExtensionForMimeType(String mimeType) {
138
switch (mimeType.toLowerCase()) {
139
case "image/jpeg": return ".jpg";
140
case "image/png": return ".png";
141
case "image/gif": return ".gif";
142
case "application/pdf": return ".pdf";
143
case "text/plain": return ".txt";
144
default: return ".dat";
145
}
146
}
147
148
public Map<String, String> getAttachmentMap() {
149
return new HashMap<>(attachmentMap);
150
}
151
}
152
153
// Usage with marshaller
154
JAXBContext context = JAXBContext.newInstance(Document.class);
155
Marshaller marshaller = context.createMarshaller();
156
157
File attachmentDir = new File("attachments");
158
FileAttachmentMarshaller attachmentMarshaller = new FileAttachmentMarshaller(attachmentDir);
159
marshaller.setAttachmentMarshaller(attachmentMarshaller);
160
161
Document document = new Document();
162
document.setContent("Document content");
163
document.setImage(new DataHandler(new FileDataSource("large-image.jpg")));
164
165
marshaller.marshal(document, new File("document.xml"));
166
167
// Review created attachments
168
Map<String, String> attachments = attachmentMarshaller.getAttachmentMap();
169
System.out.println("Created attachments: " + attachments);
170
```
171
172
### AttachmentUnmarshaller
173
174
The AttachmentUnmarshaller enables processing of optimized binary data during unmarshalling by resolving attachment references.
175
176
```java { .api }
177
public abstract class AttachmentUnmarshaller {
178
// Retrieve attachment as DataHandler
179
public abstract javax.activation.DataHandler getAttachmentAsDataHandler(String cid);
180
181
// Retrieve attachment as byte array
182
public abstract byte[] getAttachmentAsByteArray(String cid);
183
184
// XOP package detection
185
public boolean isXOPPackage();
186
}
187
```
188
189
**Usage Examples:**
190
191
```java
192
// Custom AttachmentUnmarshaller implementation
193
public class FileAttachmentUnmarshaller extends AttachmentUnmarshaller {
194
private final File attachmentDir;
195
private final Map<String, String> attachmentMap;
196
197
public FileAttachmentUnmarshaller(File attachmentDir, Map<String, String> attachmentMap) {
198
this.attachmentDir = attachmentDir;
199
this.attachmentMap = new HashMap<>(attachmentMap);
200
}
201
202
@Override
203
public DataHandler getAttachmentAsDataHandler(String cid) {
204
String filePath = attachmentMap.get(cid);
205
if (filePath == null) {
206
throw new IllegalArgumentException("Attachment not found: " + cid);
207
}
208
209
File attachmentFile = new File(filePath);
210
if (!attachmentFile.exists()) {
211
throw new IllegalArgumentException("Attachment file not found: " + filePath);
212
}
213
214
System.out.printf("Retrieving attachment as DataHandler: %s -> %s%n", cid, filePath);
215
216
return new DataHandler(new FileDataSource(attachmentFile));
217
}
218
219
@Override
220
public byte[] getAttachmentAsByteArray(String cid) {
221
String filePath = attachmentMap.get(cid);
222
if (filePath == null) {
223
throw new IllegalArgumentException("Attachment not found: " + cid);
224
}
225
226
File attachmentFile = new File(filePath);
227
if (!attachmentFile.exists()) {
228
throw new IllegalArgumentException("Attachment file not found: " + filePath);
229
}
230
231
try {
232
System.out.printf("Retrieving attachment as byte array: %s -> %s%n", cid, filePath);
233
return Files.readAllBytes(attachmentFile.toPath());
234
235
} catch (IOException e) {
236
throw new RuntimeException("Failed to read attachment: " + filePath, e);
237
}
238
}
239
240
@Override
241
public boolean isXOPPackage() {
242
return true; // This unmarshaller handles XOP packages
243
}
244
}
245
246
// Usage with unmarshaller
247
JAXBContext context = JAXBContext.newInstance(Document.class);
248
Unmarshaller unmarshaller = context.createUnmarshaller();
249
250
// Set up attachment unmarshaller with attachment map from marshalling
251
File attachmentDir = new File("attachments");
252
Map<String, String> attachmentMap = loadAttachmentMap(); // Load from previous marshalling
253
FileAttachmentUnmarshaller attachmentUnmarshaller =
254
new FileAttachmentUnmarshaller(attachmentDir, attachmentMap);
255
256
unmarshaller.setAttachmentUnmarshaller(attachmentUnmarshaller);
257
258
Document document = (Document) unmarshaller.unmarshal(new File("document.xml"));
259
260
// Access binary data
261
DataHandler imageData = document.getImage();
262
if (imageData != null) {
263
try (InputStream is = imageData.getInputStream();
264
FileOutputStream fos = new FileOutputStream("restored-image.jpg")) {
265
266
byte[] buffer = new byte[8192];
267
int bytesRead;
268
while ((bytesRead = is.read(buffer)) != -1) {
269
fos.write(buffer, 0, bytesRead);
270
}
271
}
272
}
273
```
274
275
### Annotation Support
276
277
Jakarta XML Binding provides annotations for controlling binary attachment behavior.
278
279
```java { .api }
280
@Target({ElementType.FIELD, ElementType.METHOD})
281
@Retention(RetentionPolicy.RUNTIME)
282
public @interface XmlAttachmentRef {
283
}
284
285
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE})
286
@Retention(RetentionPolicy.RUNTIME)
287
public @interface XmlInlineBinaryData {
288
}
289
290
@Target({ElementType.FIELD, ElementType.METHOD})
291
@Retention(RetentionPolicy.RUNTIME)
292
public @interface XmlMimeType {
293
String value();
294
}
295
```
296
297
**Annotation Usage Examples:**
298
299
```java
300
@XmlRootElement
301
public class Document {
302
@XmlElement
303
private String title;
304
305
// Binary data as attachment reference
306
@XmlAttachmentRef
307
@XmlMimeType("image/jpeg")
308
private DataHandler profileImage;
309
310
// Force inline encoding (disable optimization)
311
@XmlInlineBinaryData
312
private byte[] signature;
313
314
// Large document with MIME type specification
315
@XmlAttachmentRef
316
@XmlMimeType("application/pdf")
317
private DataHandler documentPdf;
318
319
// Small binary data (will be inlined)
320
@XmlElement
321
private byte[] checksum;
322
323
// Constructors, getters, setters...
324
public Document() {}
325
326
public String getTitle() { return title; }
327
public void setTitle(String title) { this.title = title; }
328
329
public DataHandler getProfileImage() { return profileImage; }
330
public void setProfileImage(DataHandler profileImage) { this.profileImage = profileImage; }
331
332
public byte[] getSignature() { return signature; }
333
public void setSignature(byte[] signature) { this.signature = signature; }
334
335
public DataHandler getDocumentPdf() { return documentPdf; }
336
public void setDocumentPdf(DataHandler documentPdf) { this.documentPdf = documentPdf; }
337
338
public byte[] getChecksum() { return checksum; }
339
public void setChecksum(byte[] checksum) { this.checksum = checksum; }
340
}
341
```
342
343
### MTOM Configuration and Usage
344
345
MTOM (Message Transmission Optimization Mechanism) provides efficient binary data transmission by avoiding base64 encoding.
346
347
**Complete MTOM Example:**
348
349
```java
350
public class MTOMExample {
351
352
public static void demonstrateMTOM() throws Exception {
353
// Create document with large binary data
354
Document document = new Document();
355
document.setTitle("Sample Document");
356
357
// Add large image as attachment
358
File imageFile = new File("large-photo.jpg");
359
DataHandler imageHandler = new DataHandler(new FileDataSource(imageFile));
360
document.setProfileImage(imageHandler);
361
362
// Add PDF document as attachment
363
File pdfFile = new File("specifications.pdf");
364
DataHandler pdfHandler = new DataHandler(new FileDataSource(pdfFile));
365
document.setDocumentPdf(pdfHandler);
366
367
// Add small signature (will be inlined)
368
document.setSignature("SIGNATURE".getBytes("UTF-8"));
369
370
// Add checksum (small, no optimization)
371
document.setChecksum(calculateChecksum(document));
372
373
// Marshall with MTOM support
374
JAXBContext context = JAXBContext.newInstance(Document.class);
375
Marshaller marshaller = context.createMarshaller();
376
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
377
378
File attachmentDir = new File("mtom-attachments");
379
FileAttachmentMarshaller attachmentMarshaller =
380
new FileAttachmentMarshaller(attachmentDir);
381
marshaller.setAttachmentMarshaller(attachmentMarshaller);
382
383
File xmlOutput = new File("document-mtom.xml");
384
marshaller.marshal(document, xmlOutput);
385
386
System.out.println("Marshalled document with MTOM attachments");
387
System.out.println("XML file: " + xmlOutput.getAbsolutePath());
388
System.out.println("Attachments: " + attachmentMarshaller.getAttachmentMap());
389
390
// Unmarshall with MTOM support
391
Unmarshaller unmarshaller = context.createUnmarshaller();
392
FileAttachmentUnmarshaller attachmentUnmarshaller =
393
new FileAttachmentUnmarshaller(attachmentDir, attachmentMarshaller.getAttachmentMap());
394
unmarshaller.setAttachmentUnmarshaller(attachmentUnmarshaller);
395
396
Document restoredDocument = (Document) unmarshaller.unmarshal(xmlOutput);
397
398
System.out.println("Unmarshalled document: " + restoredDocument.getTitle());
399
400
// Verify binary data integrity
401
verifyBinaryData(restoredDocument, imageFile, pdfFile);
402
}
403
404
private static byte[] calculateChecksum(Document document) {
405
// Simple checksum calculation
406
return document.getTitle().getBytes().length > 0 ?
407
new byte[]{0x12, 0x34, 0x56, 0x78} :
408
new byte[]{0x00, 0x00, 0x00, 0x00};
409
}
410
411
private static void verifyBinaryData(Document document, File originalImage, File originalPdf)
412
throws Exception {
413
// Verify image data
414
if (document.getProfileImage() != null) {
415
try (InputStream original = new FileInputStream(originalImage);
416
InputStream restored = document.getProfileImage().getInputStream()) {
417
418
if (streamsEqual(original, restored)) {
419
System.out.println("✓ Image data integrity verified");
420
} else {
421
System.out.println("✗ Image data integrity check failed");
422
}
423
}
424
}
425
426
// Verify PDF data
427
if (document.getDocumentPdf() != null) {
428
try (InputStream original = new FileInputStream(originalPdf);
429
InputStream restored = document.getDocumentPdf().getInputStream()) {
430
431
if (streamsEqual(original, restored)) {
432
System.out.println("✓ PDF data integrity verified");
433
} else {
434
System.out.println("✗ PDF data integrity check failed");
435
}
436
}
437
}
438
439
// Verify signature (inlined data)
440
if (document.getSignature() != null) {
441
String signature = new String(document.getSignature(), "UTF-8");
442
if ("SIGNATURE".equals(signature)) {
443
System.out.println("✓ Signature data integrity verified");
444
} else {
445
System.out.println("✗ Signature data integrity check failed");
446
}
447
}
448
}
449
450
private static boolean streamsEqual(InputStream is1, InputStream is2) throws IOException {
451
byte[] buffer1 = new byte[8192];
452
byte[] buffer2 = new byte[8192];
453
454
int bytesRead1, bytesRead2;
455
while ((bytesRead1 = is1.read(buffer1)) != -1) {
456
bytesRead2 = is2.read(buffer2);
457
458
if (bytesRead1 != bytesRead2) {
459
return false;
460
}
461
462
for (int i = 0; i < bytesRead1; i++) {
463
if (buffer1[i] != buffer2[i]) {
464
return false;
465
}
466
}
467
}
468
469
return is2.read() == -1; // Ensure second stream is also at end
470
}
471
}
472
```
473
474
### Web Service Integration
475
476
MTOM and SwA are commonly used in web service scenarios for efficient binary data transmission.
477
478
```java
479
// JAX-WS web service with MTOM support
480
@WebService
481
@MTOM(enabled = true)
482
public class DocumentService {
483
484
@WebMethod
485
public Document uploadDocument(
486
@WebParam(name = "title") String title,
487
@WebParam(name = "content") DataHandler content
488
) {
489
Document document = new Document();
490
document.setTitle(title);
491
document.setDocumentPdf(content);
492
493
// Process document...
494
return document;
495
}
496
497
@WebMethod
498
public DataHandler downloadDocument(
499
@WebParam(name = "documentId") String documentId
500
) {
501
// Retrieve document...
502
File documentFile = new File("documents/" + documentId + ".pdf");
503
return new DataHandler(new FileDataSource(documentFile));
504
}
505
}
506
507
// Client-side MTOM usage
508
@WebServiceClient
509
public class DocumentServiceClient {
510
511
public void uploadLargeDocument() throws Exception {
512
DocumentService service = new DocumentService();
513
DocumentServicePortType port = service.getDocumentServicePort();
514
515
// Enable MTOM on client side
516
BindingProvider bindingProvider = (BindingProvider) port;
517
bindingProvider.getRequestContext().put(
518
JAXWSProperties.MTOM_THRESOLD_VALUE,
519
1024 // Use MTOM for attachments > 1KB
520
);
521
522
// Upload large document
523
File largeFile = new File("specifications.pdf");
524
DataHandler fileHandler = new DataHandler(new FileDataSource(largeFile));
525
526
Document result = port.uploadDocument("Specifications", fileHandler);
527
System.out.println("Uploaded document: " + result.getTitle());
528
529
// Download document
530
DataHandler downloadedContent = port.downloadDocument(result.getId());
531
532
// Save downloaded content
533
try (InputStream is = downloadedContent.getInputStream();
534
FileOutputStream fos = new FileOutputStream("downloaded-specs.pdf")) {
535
536
byte[] buffer = new byte[8192];
537
int bytesRead;
538
while ((bytesRead = is.read(buffer)) != -1) {
539
fos.write(buffer, 0, bytesRead);
540
}
541
}
542
}
543
}
544
```
545
546
### Performance Considerations
547
548
Binary attachment optimization provides significant performance benefits for large data:
549
550
```java
551
// Performance comparison utility
552
public class AttachmentPerformanceComparison {
553
554
public static void compareMethods(File binaryFile) throws Exception {
555
byte[] fileData = Files.readAllBytes(binaryFile.toPath());
556
557
// Method 1: Inline base64 encoding (traditional)
558
long startTime = System.currentTimeMillis();
559
Document docInline = new Document();
560
docInline.setTitle("Inline Document");
561
docInline.setSignature(fileData); // Will be base64 encoded
562
563
JAXBContext context = JAXBContext.newInstance(Document.class);
564
Marshaller marshallerInline = context.createMarshaller();
565
566
StringWriter inlineWriter = new StringWriter();
567
marshallerInline.marshal(docInline, inlineWriter);
568
String inlineXml = inlineWriter.toString();
569
570
long inlineTime = System.currentTimeMillis() - startTime;
571
long inlineSize = inlineXml.getBytes("UTF-8").length;
572
573
// Method 2: MTOM attachment (optimized)
574
startTime = System.currentTimeMillis();
575
Document docMtom = new Document();
576
docMtom.setTitle("MTOM Document");
577
docMtom.setProfileImage(new DataHandler(new FileDataSource(binaryFile)));
578
579
Marshaller marshallerMtom = context.createMarshaller();
580
FileAttachmentMarshaller attachmentMarshaller =
581
new FileAttachmentMarshaller(new File("temp-attachments"));
582
marshallerMtom.setAttachmentMarshaller(attachmentMarshaller);
583
584
StringWriter mtomWriter = new StringWriter();
585
marshallerMtom.marshal(docMtom, mtomWriter);
586
String mtomXml = mtomWriter.toString();
587
588
long mtomTime = System.currentTimeMillis() - startTime;
589
long mtomSize = mtomXml.getBytes("UTF-8").length;
590
591
// Report results
592
System.out.printf("File size: %d bytes%n", fileData.length);
593
System.out.printf("Inline method: %d ms, XML size: %d bytes%n", inlineTime, inlineSize);
594
System.out.printf("MTOM method: %d ms, XML size: %d bytes%n", mtomTime, mtomSize);
595
System.out.printf("Size reduction: %.1f%%%n",
596
100.0 * (inlineSize - mtomSize) / inlineSize);
597
System.out.printf("Time improvement: %.1f%%%n",
598
100.0 * (inlineTime - mtomTime) / inlineTime);
599
}
600
}
601
```
602
603
Binary attachments are particularly beneficial when:
604
- Processing files larger than 1KB (common MTOM threshold)
605
- Working with images, documents, or other binary content
606
- Transmitting data over networks where bandwidth is constrained
607
- Integrating with web services that support MTOM/SwA standards