Jakarta Mail API provides a platform-independent and protocol-independent framework to build mail and messaging applications
—
This document covers folder management, message storage operations, hierarchical navigation, and folder-based message manipulation in the Jakarta Mail API.
The Folder class represents a container for messages with support for hierarchical organization and various access modes.
abstract class Folder implements AutoCloseable {
public abstract String getName() throws MessagingException;
public abstract String getFullName() throws MessagingException;
public URLName getURLName() throws MessagingException;
public abstract Folder getParent() throws MessagingException;
public abstract boolean exists() throws MessagingException;
}abstract class Folder {
public abstract Folder[] list() throws MessagingException;
public abstract Folder[] list(String pattern) throws MessagingException;
public abstract Folder[] listSubscribed() throws MessagingException;
public abstract Folder[] listSubscribed(String pattern) throws MessagingException;
public abstract char getSeparator() throws MessagingException;
public abstract int getType() throws MessagingException;
public abstract boolean create(int type) throws MessagingException;
public abstract boolean delete(boolean recurse) throws MessagingException;
public abstract boolean renameTo(Folder f) throws MessagingException;
}Folder Types:
HOLDS_MESSAGES (0x01) - Can contain messagesHOLDS_FOLDERS (0x02) - Can contain subfoldersAccess Modes:
READ_ONLY (1) - Read-only accessREAD_WRITE (2) - Read-write accessabstract class Folder {
public abstract void open(int mode) throws MessagingException;
public abstract void close() throws MessagingException;
public abstract void close(boolean expunge) throws MessagingException;
public abstract boolean isOpen();
public Store getStore();
public int getMode();
public String toString();
}Store store = session.getStore("imap");
store.connect("imap.example.com", "username", "password");
// Navigate folder hierarchy
Folder defaultFolder = store.getDefaultFolder();
Folder[] folders = defaultFolder.list("*");
// Work with INBOX
Folder inbox = store.getFolder("INBOX");
if (inbox.exists()) {
inbox.open(Folder.READ_WRITE);
// Folder operations here
inbox.close(false); // Don't expunge on close
}
// Create new folder
Folder customFolder = store.getFolder("My Messages");
if (!customFolder.exists()) {
customFolder.create(Folder.HOLDS_MESSAGES);
}abstract class Folder {
public abstract int getMessageCount() throws MessagingException;
public abstract int getNewMessageCount() throws MessagingException;
public abstract int getUnreadMessageCount() throws MessagingException;
public abstract int getDeletedMessageCount() throws MessagingException;
public abstract Message getMessage(int msgnum) throws MessagingException;
public abstract Message[] getMessages() throws MessagingException;
public abstract Message[] getMessages(int start, int end) throws MessagingException;
public abstract Message[] getMessages(int[] msgnums) throws MessagingException;
}abstract class Folder {
public abstract boolean hasNewMessages() throws MessagingException;
public abstract Flags getPermanentFlags() throws MessagingException;
public void fetch(Message[] msgs, FetchProfile fp) throws MessagingException;
public void setFlags(Message[] msgs, Flags flag, boolean value) throws MessagingException;
public void setFlags(int start, int end, Flags flag, boolean value) throws MessagingException;
public void setFlags(int[] msgnums, Flags flag, boolean value) throws MessagingException;
}folder.open(Folder.READ_ONLY);
// Get message counts
int totalMessages = folder.getMessageCount();
int newMessages = folder.getNewMessageCount();
int unreadMessages = folder.getUnreadMessageCount();
System.out.println("Total: " + totalMessages + ", New: " + newMessages + ", Unread: " + unreadMessages);
// Get all messages
Message[] messages = folder.getMessages();
// Get recent messages (last 10)
int count = folder.getMessageCount();
Message[] recent = folder.getMessages(Math.max(1, count - 9), count);
// Mark messages as read
folder.setFlags(messages, new Flags(Flags.Flag.SEEN), true);abstract class Folder {
public abstract void appendMessages(Message[] msgs) throws MessagingException;
public void copyMessages(Message[] msgs, Folder folder) throws MessagingException;
}abstract class Folder {
public abstract Message[] expunge() throws MessagingException;
public Message[] expunge(Message[] msgs) throws MessagingException;
}// Append new messages to folder
Message[] newMessages = createMessages(); // Your message creation logic
folder.appendMessages(newMessages);
// Copy messages to another folder
Folder sentFolder = store.getFolder("Sent");
sentFolder.open(Folder.READ_WRITE);
folder.copyMessages(messages, sentFolder);
// Delete messages (mark as deleted, then expunge)
folder.setFlags(messages, new Flags(Flags.Flag.DELETED), true);
Message[] expunged = folder.expunge(); // Permanently removes deleted messagesThe UIDFolder interface provides support for message UIDs (Unique Identifiers).
interface UIDFolder {
public long getUID(Message message) throws MessagingException;
public Message getMessageByUID(long uid) throws MessagingException;
public Message[] getMessagesByUID(long start, long end) throws MessagingException;
public Message[] getMessagesByUID(long[] uids) throws MessagingException;
public long getUIDValidity() throws MessagingException;
public long getUIDNext() throws MessagingException;
static class FetchProfileItem extends FetchProfile.Item {
static final FetchProfileItem UID;
}
}if (folder instanceof UIDFolder) {
UIDFolder uidFolder = (UIDFolder) folder;
// Get UID for a message
long uid = uidFolder.getUID(message);
// Retrieve message by UID
Message msgByUID = uidFolder.getMessageByUID(uid);
// Get UID validity (changes when folder is recreated)
long uidValidity = uidFolder.getUIDValidity();
// Get messages by UID range
Message[] msgs = uidFolder.getMessagesByUID(100L, 200L);
}The Quota class provides support for storage quotas on folders and stores.
class Quota {
public String quotaRoot;
public Resource[] resources;
public Quota(String quotaRoot);
static class Resource {
public String name;
public long usage;
public long limit;
public Resource(String name, long usage, long limit);
}
}
interface QuotaAwareStore {
public Quota[] getQuota(String quotaRoot) throws MessagingException;
public void setQuota(Quota quota) throws MessagingException;
}if (store instanceof QuotaAwareStore) {
QuotaAwareStore quotaStore = (QuotaAwareStore) store;
// Get quota information
Quota[] quotas = quotaStore.getQuota("INBOX");
for (Quota quota : quotas) {
for (Quota.Resource resource : quota.resources) {
System.out.println("Resource: " + resource.name);
System.out.println("Usage: " + resource.usage + "/" + resource.limit);
}
}
}abstract class Folder {
public abstract Message[] search(SearchTerm term) throws MessagingException;
public abstract Message[] search(SearchTerm term, Message[] msgs) throws MessagingException;
}// Search for messages from specific sender
SearchTerm fromTerm = new FromStringTerm("john@example.com");
Message[] fromJohn = folder.search(fromTerm);
// Search for unread messages with specific subject
SearchTerm unreadTerm = new FlagTerm(new Flags(Flags.Flag.SEEN), false);
SearchTerm subjectTerm = new SubjectTerm("Important");
SearchTerm combinedTerm = new AndTerm(unreadTerm, subjectTerm);
Message[] results = folder.search(combinedTerm);
// Search within a subset of messages
Message[] recentMessages = folder.getMessages(100, 200);
Message[] filteredResults = folder.search(fromTerm, recentMessages);The Store class provides access to message stores and folder hierarchies.
abstract class Store extends Service {
public abstract Folder getDefaultFolder() throws MessagingException;
public abstract Folder getFolder(String name) throws MessagingException;
public abstract Folder getFolder(URLName url) throws MessagingException;
}abstract class Store {
public Folder[] getPersonalNamespaces() throws MessagingException;
public Folder[] getUserNamespaces(String user) throws MessagingException;
public Folder[] getSharedNamespaces() throws MessagingException;
}abstract class Store {
public void addStoreListener(StoreListener l);
public void removeStoreListener(StoreListener l);
public void addFolderListener(FolderListener l);
public void removeFolderListener(FolderListener l);
}Store store = session.getStore("imap");
store.connect();
// Get namespace folders
Folder[] personal = store.getPersonalNamespaces();
Folder[] shared = store.getSharedNamespaces();
System.out.println("Personal folders:");
for (Folder f : personal) {
listFolderHierarchy(f, 0);
}
System.out.println("Shared folders:");
for (Folder f : shared) {
listFolderHierarchy(f, 0);
}
store.close();
// Helper method for recursive folder listing
private void listFolderHierarchy(Folder folder, int depth) throws MessagingException {
String indent = " ".repeat(depth);
System.out.println(indent + folder.getName() + " (" + folder.getMessageCount() + " messages)");
if ((folder.getType() & Folder.HOLDS_FOLDERS) != 0) {
Folder[] subfolders = folder.list();
for (Folder subfolder : subfolders) {
listFolderHierarchy(subfolder, depth + 1);
}
}
}The FetchProfile class optimizes message retrieval by specifying which data to prefetch.
class FetchProfile {
public FetchProfile();
public void add(Item item);
public void add(String headerName);
public boolean contains(Item item);
public boolean contains(String headerName);
public Item[] getItems();
public String[] getHeaderNames();
static class Item {
static final Item ENVELOPE;
static final Item CONTENT_INFO;
static final Item FLAGS;
static final Item SIZE;
protected Item(String name);
}
}// Create fetch profile for efficient access
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE); // From, To, Subject, Date
fp.add(FetchProfile.Item.FLAGS); // Message flags
fp.add("X-Priority"); // Custom header
// Fetch data efficiently
Message[] messages = folder.getMessages();
folder.fetch(messages, fp);
// Access prefetched data
for (Message msg : messages) {
// These accesses are now efficient (data already fetched)
System.out.println("From: " + msg.getFrom()[0]);
System.out.println("Subject: " + msg.getSubject());
System.out.println("Seen: " + msg.isSet(Flags.Flag.SEEN));
}abstract class Folder {
public void setSubscribed(boolean subscribe) throws MessagingException;
public boolean isSubscribed() throws MessagingException;
}interface Rights {
public Right[] getRights();
static class Right {
static final Right LOOKUP; // 'l' - Lookup/visible
static final Right READ; // 'r' - Read
static final Right KEEP_SEEN; // 's' - Keep \Seen flag
static final Right WRITE; // 'w' - Write/flag except \Seen
static final Right INSERT; // 'i' - Insert/copy messages
static final Right POST; // 'p' - Post messages
static final Right CREATE; // 'c' - Create subfolders
static final Right DELETE; // 'd' - Delete messages
static final Right ADMINISTER;// 'a' - Administer rights
}
}
interface ACL {
public void addRights(ACL.Rights rights) throws MessagingException;
public void removeRights(ACL.Rights rights) throws MessagingException;
public void setRights(ACL.Rights rights) throws MessagingException;
public ACL.Rights[] listRights(String name) throws MessagingException;
public ACL.Rights myRights(String name) throws MessagingException;
}class FolderClosedException extends MessagingException {
public FolderClosedException(Folder folder);
public FolderClosedException(Folder folder, String message);
public Folder getFolder();
}
class FolderNotFoundException extends MessagingException {
public FolderNotFoundException();
public FolderNotFoundException(String s);
public FolderNotFoundException(Folder f);
public FolderNotFoundException(String s, Folder f);
public Folder getFolder();
}
class ReadOnlyFolderException extends MessagingException {
public ReadOnlyFolderException(Folder folder);
public ReadOnlyFolderException(Folder folder, String message);
public Folder getFolder();
}
class StoreClosedException extends MessagingException {
public StoreClosedException(Store store);
public StoreClosedException(Store store, String message);
public Store getStore();
}
class MessageRemovedException extends MessagingException {
public MessageRemovedException();
public MessageRemovedException(String s);
}try {
folder.open(Folder.READ_WRITE);
Message[] messages = folder.getMessages();
for (Message msg : messages) {
try {
System.out.println("Subject: " + msg.getSubject());
} catch (MessageRemovedException e) {
System.out.println("Message was expunged: " + e.getMessage());
continue;
}
}
folder.close();
} catch (FolderNotFoundException e) {
System.err.println("Folder not found: " + e.getFolder().getFullName());
} catch (ReadOnlyFolderException e) {
System.err.println("Folder is read-only: " + e.getFolder().getName());
} catch (FolderClosedException e) {
System.err.println("Folder was closed: " + e.getFolder().getName());
} catch (StoreClosedException e) {
System.err.println("Store connection closed: " + e.getStore().getURLName());
}public class FolderOperationsExample {
public void manageFolders(Session session) throws MessagingException {
Store store = session.getStore("imap");
store.connect("imap.example.com", "username", "password");
try {
// Create organizational folders
createFolderStructure(store);
// Process messages in INBOX
processInbox(store);
// Archive old messages
archiveOldMessages(store);
} finally {
store.close();
}
}
private void createFolderStructure(Store store) throws MessagingException {
String[] folderNames = {"Archive", "Projects", "Personal"};
for (String name : folderNames) {
Folder folder = store.getFolder(name);
if (!folder.exists()) {
boolean created = folder.create(Folder.HOLDS_MESSAGES | Folder.HOLDS_FOLDERS);
System.out.println("Created folder '" + name + "': " + created);
}
}
}
private void processInbox(Store store) throws MessagingException {
Folder inbox = store.getFolder("INBOX");
inbox.open(Folder.READ_WRITE);
try {
Message[] messages = inbox.getMessages();
System.out.println("Processing " + messages.length + " messages");
// Use fetch profile for efficiency
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE);
fp.add(FetchProfile.Item.FLAGS);
inbox.fetch(messages, fp);
for (Message msg : messages) {
processMessage(msg, store);
}
} finally {
inbox.close(false);
}
}
private void processMessage(Message message, Store store) throws MessagingException {
// Example: Move project-related emails to Projects folder
String subject = message.getSubject();
if (subject != null && subject.toLowerCase().contains("project")) {
Folder projectsFolder = store.getFolder("Projects");
projectsFolder.open(Folder.READ_WRITE);
try {
Message[] msgs = {message};
message.getFolder().copyMessages(msgs, projectsFolder);
message.setFlag(Flags.Flag.DELETED, true);
} finally {
projectsFolder.close(false);
}
}
}
private void archiveOldMessages(Store store) throws MessagingException {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MONTH, -6); // 6 months ago
Date cutoffDate = cal.getTime();
// Search for old messages
SearchTerm oldTerm = new ReceivedDateTerm(ComparisonTerm.LT, cutoffDate);
Folder inbox = store.getFolder("INBOX");
inbox.open(Folder.READ_WRITE);
try {
Message[] oldMessages = inbox.search(oldTerm);
if (oldMessages.length > 0) {
Folder archiveFolder = store.getFolder("Archive");
archiveFolder.open(Folder.READ_WRITE);
try {
inbox.copyMessages(oldMessages, archiveFolder);
inbox.setFlags(oldMessages, new Flags(Flags.Flag.DELETED), true);
inbox.expunge();
System.out.println("Archived " + oldMessages.length + " old messages");
} finally {
archiveFolder.close(false);
}
}
} finally {
inbox.close(false);
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-sun-mail--jakarta-mail-api@1.6.1