Jakarta Mail API provides a platform-independent and protocol-independent framework to build mail and messaging applications
—
This document covers the comprehensive event-driven programming capabilities of the Jakarta Mail API, including connection events, message changes, folder operations, and transport notifications.
The Jakarta Mail API provides an event-driven architecture for monitoring mail operations. Events are fired for connections, folder changes, message modifications, and transport operations.
abstract class MailEvent extends EventObject {
public MailEvent(Object source);
public abstract void dispatch(Object listener);
}All mail events extend this base class and implement the dispatch method to deliver themselves to appropriate listener methods.
Connection events are fired when mail service connections are opened, closed, or disconnected unexpectedly.
class ConnectionEvent extends MailEvent {
public static final int OPENED = 1;
public static final int DISCONNECTED = 2;
public static final int CLOSED = 3;
public ConnectionEvent(Object source, int type);
public int getType();
public void dispatch(Object listener);
}interface ConnectionListener extends EventListener {
public void opened(ConnectionEvent e);
public void disconnected(ConnectionEvent e);
public void closed(ConnectionEvent e);
}abstract class ConnectionAdapter implements ConnectionListener {
public void opened(ConnectionEvent e) {}
public void disconnected(ConnectionEvent e) {}
public void closed(ConnectionEvent e) {}
}public class ConnectionMonitor extends ConnectionAdapter {
@Override
public void opened(ConnectionEvent e) {
System.out.println("Connection opened: " + e.getSource());
}
@Override
public void disconnected(ConnectionEvent e) {
System.err.println("Connection lost: " + e.getSource());
handleDisconnection();
}
@Override
public void closed(ConnectionEvent e) {
System.out.println("Connection closed: " + e.getSource());
}
private void handleDisconnection() {
// Implement reconnection logic
System.out.println("Attempting to reconnect...");
}
}
// Register connection listener
Store store = session.getStore("imap");
store.addConnectionListener(new ConnectionMonitor());
store.connect();Folder events are fired when folders are created, deleted, or renamed in the message store.
class FolderEvent extends MailEvent {
public static final int CREATED = 1;
public static final int DELETED = 2;
public static final int RENAMED = 3;
protected Folder folder;
protected Folder newFolder;
public FolderEvent(Object source, Folder folder, int type);
public FolderEvent(Object source, Folder oldFolder, Folder newFolder, int type);
public int getType();
public Folder getFolder();
public Folder getNewFolder();
public void dispatch(Object listener);
}interface FolderListener extends EventListener {
public void folderCreated(FolderEvent e);
public void folderDeleted(FolderEvent e);
public void folderRenamed(FolderEvent e);
}abstract class FolderAdapter implements FolderListener {
public void folderCreated(FolderEvent e) {}
public void folderDeleted(FolderEvent e) {}
public void folderRenamed(FolderEvent e) {}
}public class FolderMonitor extends FolderAdapter {
@Override
public void folderCreated(FolderEvent e) {
Folder newFolder = e.getFolder();
System.out.println("New folder created: " + newFolder.getFullName());
// Update folder cache or UI
updateFolderList();
}
@Override
public void folderDeleted(FolderEvent e) {
Folder deletedFolder = e.getFolder();
System.out.println("Folder deleted: " + deletedFolder.getFullName());
// Clean up references
removeFolderFromCache(deletedFolder);
}
@Override
public void folderRenamed(FolderEvent e) {
Folder oldFolder = e.getFolder();
Folder newFolder = e.getNewFolder();
System.out.println("Folder renamed: " + oldFolder.getFullName() +
" -> " + newFolder.getFullName());
// Update references
updateFolderReferences(oldFolder, newFolder);
}
private void updateFolderList() { /* Implementation */ }
private void removeFolderFromCache(Folder folder) { /* Implementation */ }
private void updateFolderReferences(Folder old, Folder newFolder) { /* Implementation */ }
}
// Register folder listener
Store store = session.getStore("imap");
store.addFolderListener(new FolderMonitor());Message count events are fired when messages are added to or removed from folders.
class MessageCountEvent extends MailEvent {
public static final int ADDED = 1;
public static final int REMOVED = 2;
protected int type;
protected boolean removed;
protected Message[] msgs;
public MessageCountEvent(Folder folder, int type, boolean removed, Message[] msgs);
public int getType();
public boolean isRemoved();
public Message[] getMessages();
public void dispatch(Object listener);
}interface MessageCountListener extends EventListener {
public void messagesAdded(MessageCountEvent e);
public void messagesRemoved(MessageCountEvent e);
}abstract class MessageCountAdapter implements MessageCountListener {
public void messagesAdded(MessageCountEvent e) {}
public void messagesRemoved(MessageCountEvent e) {}
}public class MessageMonitor extends MessageCountAdapter {
private int totalMessages = 0;
@Override
public void messagesAdded(MessageCountEvent e) {
Message[] newMessages = e.getMessages();
totalMessages += newMessages.length;
System.out.println(newMessages.length + " new messages arrived");
System.out.println("Total messages: " + totalMessages);
// Process new messages
for (Message msg : newMessages) {
try {
processNewMessage(msg);
} catch (MessagingException ex) {
System.err.println("Error processing message: " + ex.getMessage());
}
}
}
@Override
public void messagesRemoved(MessageCountEvent e) {
Message[] removedMessages = e.getMessages();
totalMessages -= removedMessages.length;
System.out.println(removedMessages.length + " messages removed");
System.out.println("Total messages: " + totalMessages);
// Clean up references
for (Message msg : removedMessages) {
cleanupMessage(msg);
}
}
private void processNewMessage(Message msg) throws MessagingException {
// Handle new message arrival
System.out.println("New message: " + msg.getSubject());
// Check for important messages
if (isImportant(msg)) {
notifyUser(msg);
}
}
private boolean isImportant(Message msg) throws MessagingException {
return msg.isSet(Flags.Flag.FLAGGED) ||
(msg.getSubject() != null && msg.getSubject().contains("URGENT"));
}
private void notifyUser(Message msg) {
// Implementation for user notification
System.out.println("IMPORTANT MESSAGE RECEIVED!");
}
private void cleanupMessage(Message msg) {
// Clean up message references
System.out.println("Cleaning up message references");
}
}
// Register message count listener
Folder folder = store.getFolder("INBOX");
folder.addMessageCountListener(new MessageMonitor());
folder.open(Folder.READ_WRITE);Message changed events are fired when message properties (flags, headers, content) are modified.
class MessageChangedEvent extends MailEvent {
protected int type;
protected Message msg;
public MessageChangedEvent(Object source, int type, Message message);
public int getType();
public Message getMessage();
public void dispatch(Object listener);
}interface MessageChangedListener extends EventListener {
public void messageChanged(MessageChangedEvent e);
}public class MessageChangeMonitor implements MessageChangedListener {
@Override
public void messageChanged(MessageChangedEvent e) {
Message msg = e.getMessage();
try {
System.out.println("Message changed: " + msg.getSubject());
// Check what changed
if (msg.isSet(Flags.Flag.SEEN)) {
System.out.println("Message marked as read");
}
if (msg.isSet(Flags.Flag.FLAGGED)) {
System.out.println("Message flagged as important");
}
if (msg.isSet(Flags.Flag.DELETED)) {
System.out.println("Message marked for deletion");
}
// Update UI or cache
updateMessageDisplay(msg);
} catch (MessagingException ex) {
System.err.println("Error handling message change: " + ex.getMessage());
}
}
private void updateMessageDisplay(Message msg) {
// Update UI representation of the message
System.out.println("Updating message display");
}
}
// Register message changed listener
folder.addMessageChangedListener(new MessageChangeMonitor());Store events provide general notifications about store-level operations.
class StoreEvent extends MailEvent {
protected int type;
protected String message;
public StoreEvent(Store store, int type, String message);
public int getType();
public String getMessage();
public void dispatch(Object listener);
}interface StoreListener extends EventListener {
public void notification(StoreEvent e);
}public class StoreMonitor implements StoreListener {
@Override
public void notification(StoreEvent e) {
System.out.println("Store notification: " + e.getMessage());
// Handle different types of store events
int eventType = e.getType();
switch (eventType) {
case StoreEvent.ALERT:
handleAlert(e.getMessage());
break;
case StoreEvent.NOTICE:
handleNotice(e.getMessage());
break;
default:
handleGenericEvent(e.getMessage());
break;
}
}
private void handleAlert(String message) {
System.err.println("STORE ALERT: " + message);
// Handle critical store alerts
}
private void handleNotice(String message) {
System.out.println("Store notice: " + message);
// Handle informational notices
}
private void handleGenericEvent(String message) {
System.out.println("Store event: " + message);
}
}
// Register store listener
store.addStoreListener(new StoreMonitor());Transport events are fired during message sending operations to report delivery status.
class TransportEvent extends MailEvent {
public static final int MESSAGE_DELIVERED = 1;
public static final int MESSAGE_NOT_DELIVERED = 2;
public static final int MESSAGE_PARTIALLY_DELIVERED = 3;
protected int type;
protected Address[] validSent;
protected Address[] validUnsent;
protected Address[] invalid;
protected Message msg;
public TransportEvent(Transport transport, int type, Address[] validSent,
Address[] validUnsent, Address[] invalid, Message msg);
public int getType();
public Address[] getValidSentAddresses();
public Address[] getValidUnsentAddresses();
public Address[] getInvalidAddresses();
public Message getMessage();
public void dispatch(Object listener);
}interface TransportListener extends EventListener {
public void messageDelivered(TransportEvent e);
public void messageNotDelivered(TransportEvent e);
public void messagePartiallyDelivered(TransportEvent e);
}abstract class TransportAdapter implements TransportListener {
public void messageDelivered(TransportEvent e) {}
public void messageNotDelivered(TransportEvent e) {}
public void messagePartiallyDelivered(TransportEvent e) {}
}public class DeliveryMonitor extends TransportAdapter {
@Override
public void messageDelivered(TransportEvent e) {
Message msg = e.getMessage();
Address[] delivered = e.getValidSentAddresses();
try {
System.out.println("Message delivered successfully: " + msg.getSubject());
System.out.println("Delivered to " + delivered.length + " recipients:");
for (Address addr : delivered) {
System.out.println(" - " + addr.toString());
}
// Log successful delivery
logDelivery(msg, delivered, true);
} catch (MessagingException ex) {
System.err.println("Error processing delivery confirmation: " + ex.getMessage());
}
}
@Override
public void messageNotDelivered(TransportEvent e) {
Message msg = e.getMessage();
Address[] failed = e.getValidUnsentAddresses();
Address[] invalid = e.getInvalidAddresses();
try {
System.err.println("Message delivery failed: " + msg.getSubject());
if (failed != null && failed.length > 0) {
System.err.println("Failed addresses:");
for (Address addr : failed) {
System.err.println(" - " + addr.toString());
}
}
if (invalid != null && invalid.length > 0) {
System.err.println("Invalid addresses:");
for (Address addr : invalid) {
System.err.println(" - " + addr.toString());
}
}
// Log failed delivery and schedule retry
logDelivery(msg, failed, false);
scheduleRetry(msg, failed);
} catch (MessagingException ex) {
System.err.println("Error processing delivery failure: " + ex.getMessage());
}
}
@Override
public void messagePartiallyDelivered(TransportEvent e) {
Message msg = e.getMessage();
Address[] delivered = e.getValidSentAddresses();
Address[] failed = e.getValidUnsentAddresses();
try {
System.out.println("Message partially delivered: " + msg.getSubject());
if (delivered != null && delivered.length > 0) {
System.out.println("Successfully delivered to:");
for (Address addr : delivered) {
System.out.println(" - " + addr.toString());
}
}
if (failed != null && failed.length > 0) {
System.err.println("Failed to deliver to:");
for (Address addr : failed) {
System.err.println(" - " + addr.toString());
}
// Retry failed addresses
scheduleRetry(msg, failed);
}
} catch (MessagingException ex) {
System.err.println("Error processing partial delivery: " + ex.getMessage());
}
}
private void logDelivery(Message msg, Address[] addresses, boolean success) {
// Log delivery status to database or file
String status = success ? "DELIVERED" : "FAILED";
System.out.println("Logging delivery: " + status);
}
private void scheduleRetry(Message msg, Address[] failedAddresses) {
// Schedule retry for failed addresses
System.out.println("Scheduling retry for " + failedAddresses.length + " addresses");
}
}
// Register transport listener
Transport transport = session.getTransport("smtp");
transport.addTransportListener(new DeliveryMonitor());public class ComprehensiveMailMonitor {
public void setupEventMonitoring(Session session) throws MessagingException {
// Setup store monitoring
Store store = session.getStore("imap");
// Add connection monitoring
store.addConnectionListener(new ConnectionMonitor());
// Add folder monitoring
store.addFolderListener(new FolderMonitor());
// Add store-level monitoring
store.addStoreListener(new StoreMonitor());
store.connect();
// Setup folder-specific monitoring
Folder inbox = store.getFolder("INBOX");
// Add message count monitoring
inbox.addMessageCountListener(new MessageMonitor());
// Add message change monitoring
inbox.addMessageChangedListener(new MessageChangeMonitor());
inbox.open(Folder.READ_WRITE);
// Setup transport monitoring
Transport transport = session.getTransport("smtp");
transport.addTransportListener(new DeliveryMonitor());
// Keep connection alive and monitor events
monitorEvents(store, inbox, transport);
}
private void monitorEvents(Store store, Folder folder, Transport transport) {
// Keep the application running to receive events
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
System.out.println("Shutting down mail monitoring...");
folder.close();
store.close();
transport.close();
} catch (MessagingException e) {
System.err.println("Error during shutdown: " + e.getMessage());
}
}));
// Keep alive with periodic operations
Timer keepAliveTimer = new Timer(true);
keepAliveTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
// Periodic check to keep connection alive
if (store.isConnected()) {
folder.getMessageCount(); // No-op to keep connection active
} else {
System.out.println("Reconnecting to store...");
store.connect();
folder.open(Folder.READ_WRITE);
}
} catch (MessagingException e) {
System.err.println("Keep-alive check failed: " + e.getMessage());
}
}
}, 60000, 60000); // Check every minute
// Main event loop
try {
System.out.println("Mail monitoring started. Press Ctrl+C to stop.");
Thread.currentThread().join(); // Keep main thread alive
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}public class EventDrivenMailClient {
private Store store;
private Folder inbox;
private List<Message> pendingMessages = new ArrayList<>();
public void startClient(Session session) throws MessagingException {
setupConnection(session);
setupEventHandlers();
startMessageProcessing();
}
private void setupConnection(Session session) throws MessagingException {
store = session.getStore("imap");
store.connect();
inbox = store.getFolder("INBOX");
inbox.open(Folder.READ_WRITE);
}
private void setupEventHandlers() {
// Handle new messages
inbox.addMessageCountListener(new MessageCountAdapter() {
@Override
public void messagesAdded(MessageCountEvent e) {
synchronized (pendingMessages) {
Collections.addAll(pendingMessages, e.getMessages());
pendingMessages.notifyAll();
}
}
});
// Handle connection issues
store.addConnectionListener(new ConnectionAdapter() {
@Override
public void disconnected(ConnectionEvent e) {
System.err.println("Connection lost! Attempting to reconnect...");
reconnect();
}
});
// Handle message changes
inbox.addMessageChangedListener(e -> {
try {
Message msg = e.getMessage();
System.out.println("Message updated: " + msg.getSubject());
} catch (MessagingException ex) {
System.err.println("Error handling message change: " + ex.getMessage());
}
});
}
private void startMessageProcessing() {
Thread processingThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
synchronized (pendingMessages) {
while (pendingMessages.isEmpty()) {
pendingMessages.wait();
}
Message msg = pendingMessages.remove(0);
processMessage(msg);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
System.err.println("Error processing message: " + e.getMessage());
}
}
});
processingThread.setDaemon(true);
processingThread.start();
}
private void processMessage(Message msg) throws MessagingException {
System.out.println("Processing new message: " + msg.getSubject());
// Auto-reply to urgent messages
if (isUrgent(msg)) {
sendAutoReply(msg);
}
// Archive old messages automatically
if (isOld(msg)) {
archiveMessage(msg);
}
// Mark as processed
msg.setFlag(Flags.Flag.SEEN, true);
}
private boolean isUrgent(Message msg) throws MessagingException {
String subject = msg.getSubject();
return subject != null && (subject.contains("URGENT") || subject.contains("ASAP"));
}
private boolean isOld(Message msg) throws MessagingException {
Date received = msg.getReceivedDate();
if (received == null) return false;
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MONTH, -6);
return received.before(cal.getTime());
}
private void sendAutoReply(Message msg) {
System.out.println("Sending auto-reply to urgent message");
// Implementation for auto-reply
}
private void archiveMessage(Message msg) throws MessagingException {
System.out.println("Archiving old message");
// Move to archive folder
Folder archive = store.getFolder("Archive");
if (!archive.exists()) {
archive.create(Folder.HOLDS_MESSAGES);
}
archive.open(Folder.READ_WRITE);
Message[] msgs = {msg};
inbox.copyMessages(msgs, archive);
msg.setFlag(Flags.Flag.DELETED, true);
archive.close(false);
}
private void reconnect() {
try {
Thread.sleep(5000); // Wait before reconnecting
if (!store.isConnected()) {
store.connect();
}
if (!inbox.isOpen()) {
inbox.open(Folder.READ_WRITE);
}
System.out.println("Reconnected successfully");
} catch (Exception e) {
System.err.println("Reconnection failed: " + e.getMessage());
}
}
public void shutdown() {
try {
if (inbox != null && inbox.isOpen()) {
inbox.close();
}
if (store != null && store.isConnected()) {
store.close();
}
} catch (MessagingException e) {
System.err.println("Error during shutdown: " + e.getMessage());
}
}
}This comprehensive event handling system allows for reactive, event-driven mail applications that can respond to real-time changes in mail stores, handle connection issues gracefully, and provide rich user experiences with immediate feedback on mail operations.
Install with Tessl CLI
npx tessl i tessl/maven-com-sun-mail--jakarta-mail-api