Jakarta Mail defines a platform-independent and protocol-independent framework to build mail and messaging applications.
—
Jakarta Mail utility classes provide helper functionality for data sources, shared streams, stream providers, and mail-specific utilities that support the core mail operations.
The ByteArrayDataSource class provides a DataSource implementation backed by a byte array, commonly used for in-memory content.
public class ByteArrayDataSource implements DataSource {
// Constructors
public ByteArrayDataSource(byte[] data, String type);
public ByteArrayDataSource(InputStream is, String type) throws IOException;
public ByteArrayDataSource(String data, String type);
// DataSource interface methods
public InputStream getInputStream() throws IOException;
public OutputStream getOutputStream() throws IOException;
public String getContentType();
public String getName();
// Additional methods
public void setName(String name);
}import jakarta.mail.util.ByteArrayDataSource;
import jakarta.mail.internet.*;
import jakarta.mail.*;
// Create data source from byte array
byte[] pdfData = loadPdfFile(); // Your PDF data
ByteArrayDataSource dataSource = new ByteArrayDataSource(pdfData, "application/pdf");
dataSource.setName("document.pdf");
// Use in MIME body part
MimeBodyPart attachment = new MimeBodyPart();
attachment.setDataHandler(new DataHandler(dataSource));
attachment.setFileName("document.pdf");
attachment.setDisposition(Part.ATTACHMENT);
// Create from string content
String htmlContent = "<html><body><h1>Hello World</h1></body></html>";
ByteArrayDataSource htmlSource = new ByteArrayDataSource(htmlContent, "text/html");
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setDataHandler(new DataHandler(htmlSource));
// Create from input stream
InputStream imageStream = getImageInputStream();
ByteArrayDataSource imageSource = new ByteArrayDataSource(imageStream, "image/png");
imageSource.setName("logo.png");
MimeBodyPart imagePart = new MimeBodyPart();
imagePart.setDataHandler(new DataHandler(imageSource));
imagePart.setDisposition(Part.INLINE);
imagePart.setHeader("Content-ID", "<logo>");Shared input streams allow multiple concurrent readers to access the same underlying data source efficiently.
public interface SharedInputStream {
// Create new stream for specified range
public InputStream newStream(long start, long end);
// Get current position in stream
public long getPosition();
}public class SharedFileInputStream extends BufferedInputStream implements SharedInputStream {
// Constructors
public SharedFileInputStream(File file) throws IOException;
public SharedFileInputStream(File file, int size) throws IOException;
public SharedFileInputStream(String file) throws IOException;
public SharedFileInputStream(String file, int size) throws IOException;
// SharedInputStream interface methods
public InputStream newStream(long start, long end);
public long getPosition();
// File access methods
protected void finalize() throws Throwable;
}public class SharedByteArrayInputStream extends ByteArrayInputStream implements SharedInputStream {
// Constructors
public SharedByteArrayInputStream(byte[] buf);
public SharedByteArrayInputStream(byte[] buf, int offset, int length);
// SharedInputStream interface methods
public InputStream newStream(long start, long end);
public long getPosition();
}import jakarta.mail.util.*;
import java.io.*;
// Create shared file input stream for large files
File largeFile = new File("large-attachment.zip");
SharedFileInputStream sharedFile = new SharedFileInputStream(largeFile);
// Multiple readers can access different parts concurrently
InputStream reader1 = sharedFile.newStream(0, 1024); // First 1KB
InputStream reader2 = sharedFile.newStream(1024, 2048); // Second 1KB
InputStream reader3 = sharedFile.newStream(2048, -1); // Rest of file
// Read concurrently without blocking
CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> {
try {
processStream(reader1);
} catch (IOException e) {
e.printStackTrace();
}
});
CompletableFuture<Void> task2 = CompletableFuture.runAsync(() -> {
try {
processStream(reader2);
} catch (IOException e) {
e.printStackTrace();
}
});
// Shared byte array for in-memory data
byte[] data = loadLargeData();
SharedByteArrayInputStream sharedBytes = new SharedByteArrayInputStream(data);
// Create multiple streams from same data
InputStream stream1 = sharedBytes.newStream(0, 100);
InputStream stream2 = sharedBytes.newStream(100, 200);
// Each stream is independent
int data1 = stream1.read(); // Reads from position 0
int data2 = stream2.read(); // Reads from position 100public interface LineInputStream {
// Read a line of text, null if end of stream
public String readLine() throws IOException;
}public interface LineOutputStream {
// Write a line of text followed by line terminator
public void writeln(String s) throws IOException;
// Write line terminator only
public void writeln() throws IOException;
}import jakarta.mail.util.*;
import java.io.*;
// Implement line-oriented processing
public class MailHeaderProcessor {
public void processHeaders(InputStream input) throws IOException {
if (input instanceof LineInputStream) {
LineInputStream lineInput = (LineInputStream) input;
String line;
while ((line = lineInput.readLine()) != null) {
if (line.trim().isEmpty()) {
break; // End of headers
}
processHeaderLine(line);
}
} else {
// Wrap in BufferedReader for line reading
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String line;
while ((line = reader.readLine()) != null) {
if (line.trim().isEmpty()) {
break;
}
processHeaderLine(line);
}
}
}
public void writeHeaders(OutputStream output, Map<String, String> headers) throws IOException {
if (output instanceof LineOutputStream) {
LineOutputStream lineOutput = (LineOutputStream) output;
for (Map.Entry<String, String> header : headers.entrySet()) {
lineOutput.writeln(header.getKey() + ": " + header.getValue());
}
lineOutput.writeln(); // Empty line to end headers
} else {
PrintWriter writer = new PrintWriter(output);
for (Map.Entry<String, String> header : headers.entrySet()) {
writer.println(header.getKey() + ": " + header.getValue());
}
writer.println();
writer.flush();
}
}
private void processHeaderLine(String line) {
System.out.println("Processing header: " + line);
}
}The StreamProvider interface allows for pluggable stream implementations.
public interface StreamProvider {
// Get input stream for reading
public InputStream getInputStream() throws IOException;
// Get output stream for writing
public OutputStream getOutputStream() throws IOException;
}import jakarta.mail.util.StreamProvider;
import java.io.*;
import java.util.ServiceLoader;
// Custom stream provider implementation
public class CustomStreamProvider implements StreamProvider {
private final File dataFile;
public CustomStreamProvider(File dataFile) {
this.dataFile = dataFile;
}
@Override
public InputStream getInputStream() throws IOException {
return new BufferedInputStream(new FileInputStream(dataFile));
}
@Override
public OutputStream getOutputStream() throws IOException {
return new BufferedOutputStream(new FileOutputStream(dataFile));
}
}
// Load stream providers via ServiceLoader
ServiceLoader<StreamProvider> providers = ServiceLoader.load(StreamProvider.class);
for (StreamProvider provider : providers) {
try (InputStream is = provider.getInputStream()) {
// Use stream from provider
processStream(is);
}
}
// Use custom provider
StreamProvider customProvider = new CustomStreamProvider(new File("data.txt"));
try (OutputStream os = customProvider.getOutputStream()) {
os.write("Hello World".getBytes());
}The FactoryFinder class provides service loading capabilities for mail-related factories.
public class FactoryFinder {
// Find factory implementation by factory ID
public static Object find(String factoryId) throws FactoryConfigurationError;
public static Object find(String factoryId, String fallbackClassName) throws FactoryConfigurationError;
// Find factory with specific class loader
public static Object find(String factoryId, String fallbackClassName, ClassLoader cl) throws FactoryConfigurationError;
// Create new instance of factory
public static Object newInstance(String className) throws FactoryConfigurationError;
public static Object newInstance(String className, ClassLoader cl) throws FactoryConfigurationError;
}import jakarta.mail.util.FactoryFinder;
// Find mail transport factory
try {
Object transportFactory = FactoryFinder.find("jakarta.mail.Transport",
"com.sun.mail.smtp.SMTPTransport");
System.out.println("Found transport factory: " + transportFactory.getClass().getName());
} catch (FactoryConfigurationError e) {
System.err.println("Failed to find transport factory: " + e.getMessage());
}
// Find store factory with fallback
try {
Object storeFactory = FactoryFinder.find("jakarta.mail.Store",
"com.sun.mail.imap.IMAPStore");
System.out.println("Found store factory: " + storeFactory.getClass().getName());
} catch (FactoryConfigurationError e) {
System.err.println("Failed to find store factory: " + e.getMessage());
}
// Create instance directly
try {
Object instance = FactoryFinder.newInstance("com.example.CustomMailProcessor");
if (instance instanceof MailProcessor) {
MailProcessor processor = (MailProcessor) instance;
processor.process();
}
} catch (FactoryConfigurationError e) {
System.err.println("Failed to create instance: " + e.getMessage());
}The MailLogger class provides logging functionality specifically designed for mail operations.
public class MailLogger {
// Constructors
public MailLogger(String name, String defaultDebug, boolean debug, PrintStream out);
public MailLogger(String name, String defaultDebug, Session session);
// Logging methods
public void log(Level level, String msg);
public void log(Level level, String msg, Object param1);
public void log(Level level, String msg, Object... params);
// Configuration access
public boolean isLoggable(Level level);
public void config(String msg);
public void fine(String msg);
public void finer(String msg);
public void finest(String msg);
// Session-based logging
public static MailLogger getLogger(String name, String defaultDebug, Session session);
public static MailLogger getLogger(String name, String defaultDebug, String debug, boolean debugOut, PrintStream out);
}import jakarta.mail.MailLogger;
import jakarta.mail.Session;
import java.util.logging.Level;
// Create logger for mail session
Properties props = new Properties();
props.put("mail.debug", "true");
Session session = Session.getInstance(props);
MailLogger logger = MailLogger.getLogger("jakarta.mail.transport", "mail.debug", session);
// Log mail operations
logger.fine("Connecting to SMTP server");
logger.config("Using SSL connection");
logger.log(Level.INFO, "Message sent successfully to {0} recipients", 5);
// Check if logging is enabled
if (logger.isLoggable(Level.FINE)) {
logger.fine("Detailed connection information: " + getConnectionDetails());
}
// Different log levels
logger.finest("Trace level message");
logger.finer("Debug level message");
logger.fine("Fine level message");
logger.config("Configuration message");
logger.log(Level.INFO, "Info level message");
logger.log(Level.WARNING, "Warning message");
logger.log(Level.SEVERE, "Error message");Beyond the core MimeUtility class, there are additional utility functions for MIME handling.
public class MimeUtilityExtensions {
// Content type utilities
public static boolean isMultipart(String contentType) {
return contentType != null && contentType.toLowerCase().startsWith("multipart/");
}
public static boolean isText(String contentType) {
return contentType != null && contentType.toLowerCase().startsWith("text/");
}
public static String extractBoundary(String contentType) {
if (contentType == null) return null;
int boundaryIndex = contentType.toLowerCase().indexOf("boundary=");
if (boundaryIndex == -1) return null;
String boundary = contentType.substring(boundaryIndex + 9);
if (boundary.startsWith("\"") && boundary.endsWith("\"")) {
boundary = boundary.substring(1, boundary.length() - 1);
}
return boundary;
}
// Charset utilities
public static String getCharset(String contentType, String defaultCharset) {
if (contentType == null) return defaultCharset;
int charsetIndex = contentType.toLowerCase().indexOf("charset=");
if (charsetIndex == -1) return defaultCharset;
String charset = contentType.substring(charsetIndex + 8);
int semicolonIndex = charset.indexOf(';');
if (semicolonIndex != -1) {
charset = charset.substring(0, semicolonIndex);
}
charset = charset.trim();
if (charset.startsWith("\"") && charset.endsWith("\"")) {
charset = charset.substring(1, charset.length() - 1);
}
return charset;
}
// File extension to MIME type mapping
public static String getContentTypeForFile(String filename) {
if (filename == null) return "application/octet-stream";
String extension = getFileExtension(filename).toLowerCase();
switch (extension) {
case "txt": return "text/plain";
case "html": case "htm": return "text/html";
case "pdf": return "application/pdf";
case "doc": return "application/msword";
case "docx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
case "xls": return "application/vnd.ms-excel";
case "xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
case "jpg": case "jpeg": return "image/jpeg";
case "png": return "image/png";
case "gif": return "image/gif";
case "zip": return "application/zip";
case "json": return "application/json";
case "xml": return "application/xml";
default: return "application/octet-stream";
}
}
private static String getFileExtension(String filename) {
int lastDot = filename.lastIndexOf('.');
return lastDot == -1 ? "" : filename.substring(lastDot + 1);
}
}public class StreamUtils {
// Copy stream with progress tracking
public static long copyStream(InputStream input, OutputStream output,
ProgressCallback callback) throws IOException {
byte[] buffer = new byte[8192];
long totalBytes = 0;
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
totalBytes += bytesRead;
if (callback != null) {
callback.onProgress(totalBytes);
}
}
return totalBytes;
}
// Read stream to byte array with size limit
public static byte[] readStreamToBytes(InputStream input, int maxSize) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] temp = new byte[1024];
int totalRead = 0;
int bytesRead;
while ((bytesRead = input.read(temp)) != -1) {
if (totalRead + bytesRead > maxSize) {
throw new IOException("Stream size exceeds maximum allowed: " + maxSize);
}
buffer.write(temp, 0, bytesRead);
totalRead += bytesRead;
}
return buffer.toByteArray();
}
// Create tee stream that writes to multiple outputs
public static OutputStream createTeeStream(OutputStream... outputs) {
return new OutputStream() {
@Override
public void write(int b) throws IOException {
for (OutputStream output : outputs) {
output.write(b);
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
for (OutputStream output : outputs) {
output.write(b, off, len);
}
}
@Override
public void flush() throws IOException {
for (OutputStream output : outputs) {
output.flush();
}
}
@Override
public void close() throws IOException {
for (OutputStream output : outputs) {
output.close();
}
}
};
}
public interface ProgressCallback {
void onProgress(long bytesProcessed);
}
}import java.io.*;
// Copy large file with progress tracking
FileInputStream input = new FileInputStream("large-file.dat");
FileOutputStream output = new FileOutputStream("copy.dat");
StreamUtils.copyStream(input, output, bytesProcessed -> {
System.out.println("Copied " + bytesProcessed + " bytes");
});
// Read attachment with size limit (10MB max)
try {
byte[] attachmentData = StreamUtils.readStreamToBytes(attachmentStream, 10 * 1024 * 1024);
// Process attachment data
} catch (IOException e) {
System.err.println("Attachment too large: " + e.getMessage());
}
// Write to multiple destinations simultaneously
FileOutputStream file1 = new FileOutputStream("backup1.dat");
FileOutputStream file2 = new FileOutputStream("backup2.dat");
OutputStream teeStream = StreamUtils.createTeeStream(file1, file2);
// Data written to teeStream goes to both files
teeStream.write("Hello World".getBytes());
teeStream.close(); // Closes both underlying streams// Configuration errors in factory finding
public class FactoryConfigurationError extends Error {
public FactoryConfigurationError();
public FactoryConfigurationError(String msg);
public FactoryConfigurationError(Exception e);
public FactoryConfigurationError(String msg, Exception e);
public Exception getException();
}import jakarta.mail.util.*;
public class SafeUtilityUsage {
public void safeFactoryLookup() {
try {
Object factory = FactoryFinder.find("com.example.MailFactory");
// Use factory
} catch (FactoryConfigurationError e) {
System.err.println("Factory configuration error: " + e.getMessage());
if (e.getException() != null) {
System.err.println("Underlying cause: " + e.getException().getMessage());
}
// Use fallback implementation
useDefaultFactory();
}
}
public void safeStreamOperations() {
SharedFileInputStream sharedStream = null;
try {
sharedStream = new SharedFileInputStream("data.txt");
// Create multiple readers safely
InputStream reader1 = sharedStream.newStream(0, 1024);
processStreamSafely(reader1);
} catch (IOException e) {
System.err.println("Stream operation failed: " + e.getMessage());
} finally {
if (sharedStream != null) {
try {
sharedStream.close();
} catch (IOException e) {
System.err.println("Error closing stream: " + e.getMessage());
}
}
}
}
private void processStreamSafely(InputStream stream) {
try (stream) { // Auto-close
byte[] buffer = new byte[1024];
int bytesRead = stream.read(buffer);
// Process data
} catch (IOException e) {
System.err.println("Error processing stream: " + e.getMessage());
}
}
private void useDefaultFactory() {
// Fallback implementation
System.out.println("Using default factory implementation");
}
}Install with Tessl CLI
npx tessl i tessl/maven-jakarta-mail--jakarta-mail-api