Jakarta Mail API provides a platform-independent and protocol-independent framework to build mail and messaging applications
—
This document covers the comprehensive message search functionality provided by the javax.mail.search package, including search terms, logical operators, and complex query construction.
The SearchTerm class is the abstract base class for all search criteria in the Jakarta Mail API.
abstract class SearchTerm implements Serializable {
public abstract boolean match(Message msg);
}The match method tests whether a message satisfies the search criteria. All search implementations override this method to provide specific matching logic.
abstract class ComparisonTerm extends SearchTerm {
public static final int LE = 1; // Less than or equal
public static final int LT = 2; // Less than
public static final int EQ = 3; // Equal
public static final int NE = 4; // Not equal
public static final int GT = 5; // Greater than
public static final int GE = 6; // Greater than or equal
protected int comparison;
public int getComparison();
public boolean match(Message msg);
}// Search for messages and apply criteria
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
SearchTerm term = new SubjectTerm("Important");
Message[] results = folder.search(term);
System.out.println("Found " + results.length + " messages with 'Important' in subject");final class AndTerm extends SearchTerm {
public AndTerm(SearchTerm t1, SearchTerm t2);
public AndTerm(SearchTerm[] terms);
public SearchTerm[] getTerms();
public boolean match(Message msg);
}final class OrTerm extends SearchTerm {
public OrTerm(SearchTerm t1, SearchTerm t2);
public OrTerm(SearchTerm[] terms);
public SearchTerm[] getTerms();
public boolean match(Message msg);
}final class NotTerm extends SearchTerm {
public NotTerm(SearchTerm t);
public SearchTerm getTerm();
public boolean match(Message msg);
}// Complex search: Messages from John OR Jane, but NOT marked as spam
SearchTerm fromJohn = new FromStringTerm("john@example.com");
SearchTerm fromJane = new FromStringTerm("jane@example.com");
SearchTerm fromEither = new OrTerm(fromJohn, fromJane);
SearchTerm spamTerm = new SubjectTerm("SPAM");
SearchTerm notSpam = new NotTerm(spamTerm);
SearchTerm finalTerm = new AndTerm(fromEither, notSpam);
Message[] results = folder.search(finalTerm);abstract class StringTerm extends SearchTerm {
protected String pattern;
protected boolean ignoreCase;
public StringTerm(String pattern);
public StringTerm(String pattern, boolean ignoreCase);
public String getPattern();
public boolean getIgnoreCase();
protected boolean match(String s);
}final class SubjectTerm extends StringTerm {
public SubjectTerm(String pattern);
public boolean match(Message msg);
}final class BodyTerm extends StringTerm {
public BodyTerm(String pattern);
public boolean match(Message msg);
}final class HeaderTerm extends StringTerm {
protected String headerName;
public HeaderTerm(String headerName, String pattern);
public String getHeaderName();
public boolean match(Message msg);
}final class MessageIDTerm extends StringTerm {
public MessageIDTerm(String pattern);
public boolean match(Message msg);
}// Search for messages with specific subject
SearchTerm subjectTerm = new SubjectTerm("Meeting");
// Case-insensitive subject search
SearchTerm caseInsensitive = new SubjectTerm("meeting", true);
// Search in message body
SearchTerm bodyTerm = new BodyTerm("quarterly report");
// Search specific header
SearchTerm priorityTerm = new HeaderTerm("X-Priority", "1");
// Search by Message-ID
SearchTerm msgIdTerm = new MessageIDTerm("12345@example.com");
// Combine multiple string searches
SearchTerm combined = new OrTerm(
new SubjectTerm("urgent"),
new BodyTerm("deadline")
);abstract class AddressTerm extends SearchTerm {
protected Address address;
public AddressTerm(Address address);
public Address getAddress();
protected boolean match(Address a);
}final class FromTerm extends AddressTerm {
public FromTerm(Address address);
public boolean match(Message msg);
}final class RecipientTerm extends AddressTerm {
protected Message.RecipientType type;
public RecipientTerm(Message.RecipientType type, Address address);
public Message.RecipientType getRecipientType();
public boolean match(Message msg);
}abstract class AddressStringTerm extends StringTerm {
public AddressStringTerm(String pattern);
}
final class FromStringTerm extends AddressStringTerm {
public FromStringTerm(String pattern);
public boolean match(Message msg);
}
final class RecipientStringTerm extends AddressStringTerm {
protected Message.RecipientType type;
public RecipientStringTerm(Message.RecipientType type, String pattern);
public Message.RecipientType getRecipientType();
public boolean match(Message msg);
}// Search by exact From address
Address johnAddr = new InternetAddress("john@example.com");
SearchTerm fromJohn = new FromTerm(johnAddr);
// Search by From address pattern (more flexible)
SearchTerm fromExample = new FromStringTerm("@example.com");
// Search by recipient (TO addresses)
SearchTerm toMary = new RecipientTerm(Message.RecipientType.TO,
new InternetAddress("mary@example.com"));
// Search by recipient pattern (CC addresses)
SearchTerm ccDomain = new RecipientStringTerm(Message.RecipientType.CC, "@company.com");
// Combine address searches
SearchTerm fromOrTo = new OrTerm(
new FromStringTerm("manager@"),
new RecipientStringTerm(Message.RecipientType.TO, "team@")
);abstract class DateTerm extends ComparisonTerm {
protected Date date;
public DateTerm(int comparison, Date date);
public Date getDate();
public int getComparison();
protected boolean match(Date d);
}final class SentDateTerm extends DateTerm {
public SentDateTerm(int comparison, Date date);
public boolean match(Message msg);
}final class ReceivedDateTerm extends DateTerm {
public ReceivedDateTerm(int comparison, Date date);
public boolean match(Message msg);
}// Create date references
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, -7);
Date oneWeekAgo = cal.getTime();
cal = Calendar.getInstance();
cal.add(Calendar.MONTH, -1);
Date oneMonthAgo = cal.getTime();
// Search for messages sent in the last week
SearchTerm recentSent = new SentDateTerm(ComparisonTerm.GT, oneWeekAgo);
// Search for messages received before one month ago
SearchTerm oldReceived = new ReceivedDateTerm(ComparisonTerm.LT, oneMonthAgo);
// Search for messages sent today
cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
Date startOfDay = cal.getTime();
SearchTerm sentToday = new SentDateTerm(ComparisonTerm.GE, startOfDay);
// Date range search (sent between two dates)
Date startDate = oneMonthAgo;
Date endDate = oneWeekAgo;
SearchTerm dateRange = new AndTerm(
new SentDateTerm(ComparisonTerm.GE, startDate),
new SentDateTerm(ComparisonTerm.LE, endDate)
);abstract class IntegerComparisonTerm extends ComparisonTerm {
protected int number;
public IntegerComparisonTerm(int comparison, int number);
public int getNumber();
public int getComparison();
protected boolean match(int i);
}final class MessageNumberTerm extends IntegerComparisonTerm {
public MessageNumberTerm(int comparison, int number);
public boolean match(Message msg);
}final class SizeTerm extends IntegerComparisonTerm {
public SizeTerm(int comparison, int size);
public boolean match(Message msg);
}// Search for large messages (> 1MB)
int oneMB = 1024 * 1024;
SearchTerm largeMsgs = new SizeTerm(ComparisonTerm.GT, oneMB);
// Search for small messages (< 10KB)
int tenKB = 10 * 1024;
SearchTerm smallMsgs = new SizeTerm(ComparisonTerm.LT, tenKB);
// Search for specific message number
SearchTerm msgNum100 = new MessageNumberTerm(ComparisonTerm.EQ, 100);
// Search for recent message numbers (> 500)
SearchTerm recentNums = new MessageNumberTerm(ComparisonTerm.GT, 500);
// Size range search
SearchTerm mediumMsgs = new AndTerm(
new SizeTerm(ComparisonTerm.GE, tenKB),
new SizeTerm(ComparisonTerm.LE, oneMB)
);final class FlagTerm extends SearchTerm {
protected Flags flags;
protected boolean set;
public FlagTerm(Flags flags, boolean set);
public Flags getFlags();
public boolean getTestSet();
public boolean match(Message msg);
}// Search for unread messages
SearchTerm unread = new FlagTerm(new Flags(Flags.Flag.SEEN), false);
// Search for flagged/important messages
SearchTerm flagged = new FlagTerm(new Flags(Flags.Flag.FLAGGED), true);
// Search for deleted messages
SearchTerm deleted = new FlagTerm(new Flags(Flags.Flag.DELETED), true);
// Search for messages that are both unread AND flagged
SearchTerm unreadFlagged = new AndTerm(
new FlagTerm(new Flags(Flags.Flag.SEEN), false),
new FlagTerm(new Flags(Flags.Flag.FLAGGED), true)
);
// Search for draft messages
SearchTerm drafts = new FlagTerm(new Flags(Flags.Flag.DRAFT), true);
// Search for messages with custom user flags
Flags customFlags = new Flags();
customFlags.add("Important");
SearchTerm customFlagged = new FlagTerm(customFlags, true);public class SearchExamples {
public Message[] findImportantUnreadMessages(Folder folder) throws MessagingException {
// Messages that are:
// - Unread (SEEN flag not set)
// - From VIP sender OR marked as high priority
// - Received in the last 30 days
// - Not marked as SPAM
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, -30);
Date thirtyDaysAgo = cal.getTime();
// Base criteria: unread and recent
SearchTerm unread = new FlagTerm(new Flags(Flags.Flag.SEEN), false);
SearchTerm recent = new ReceivedDateTerm(ComparisonTerm.GT, thirtyDaysAgo);
// VIP sender or high priority
SearchTerm vipSender = new FromStringTerm("@vip-domain.com");
SearchTerm highPriority = new HeaderTerm("X-Priority", "1");
SearchTerm important = new OrTerm(vipSender, highPriority);
// Not spam
SearchTerm notSpam = new NotTerm(new SubjectTerm("[SPAM]"));
// Combine all criteria
SearchTerm finalCriteria = new AndTerm(new SearchTerm[]{
unread, recent, important, notSpam
});
return folder.search(finalCriteria);
}
public Message[] findMessagesForArchiving(Folder folder) throws MessagingException {
// Messages to archive:
// - Older than 1 year
// - Already read
// - Not flagged as important
// - Size less than 5MB (to avoid archiving large attachments separately)
Calendar cal = Calendar.getInstance();
cal.add(Calendar.YEAR, -1);
Date oneYearAgo = cal.getTime();
SearchTerm old = new ReceivedDateTerm(ComparisonTerm.LT, oneYearAgo);
SearchTerm read = new FlagTerm(new Flags(Flags.Flag.SEEN), true);
SearchTerm notFlagged = new FlagTerm(new Flags(Flags.Flag.FLAGGED), false);
int fiveMB = 5 * 1024 * 1024;
SearchTerm smallSize = new SizeTerm(ComparisonTerm.LT, fiveMB);
return folder.search(new AndTerm(new SearchTerm[]{
old, read, notFlagged, smallSize
}));
}
public Message[] findSuspiciousMessages(Folder folder) throws MessagingException {
// Potentially suspicious messages:
// - No subject OR subject contains suspicious keywords
// - From external domain (not @mycompany.com)
// - Contains attachments (multipart message)
// - Received recently (last 7 days)
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, -7);
Date lastWeek = cal.getTime();
// Subject criteria
SearchTerm noSubject = new SubjectTerm("");
SearchTerm suspiciousSubject = new OrTerm(
new SubjectTerm("urgent", true),
new SubjectTerm("click here", true)
);
SearchTerm badSubject = new OrTerm(noSubject, suspiciousSubject);
// External sender (not from company domain)
SearchTerm externalSender = new NotTerm(new FromStringTerm("@mycompany.com"));
// Recent messages
SearchTerm recent = new ReceivedDateTerm(ComparisonTerm.GT, lastWeek);
return folder.search(new AndTerm(new SearchTerm[]{
badSubject, externalSender, recent
}));
}
}public class CustomSearchTerm extends SearchTerm {
private String[] keywords;
private boolean requireAll;
public CustomSearchTerm(String[] keywords, boolean requireAll) {
this.keywords = keywords;
this.requireAll = requireAll;
}
@Override
public boolean match(Message msg) {
try {
String content = getMessageContent(msg);
if (content == null) return false;
content = content.toLowerCase();
int matchCount = 0;
for (String keyword : keywords) {
if (content.contains(keyword.toLowerCase())) {
matchCount++;
if (!requireAll) {
return true; // Any match is sufficient
}
}
}
return requireAll ? (matchCount == keywords.length) : false;
} catch (Exception e) {
return false;
}
}
private String getMessageContent(Message msg) throws MessagingException, IOException {
StringBuilder content = new StringBuilder();
// Add subject
String subject = msg.getSubject();
if (subject != null) {
content.append(subject).append(" ");
}
// Add body content
Object msgContent = msg.getContent();
if (msgContent instanceof String) {
content.append((String) msgContent);
} else if (msgContent instanceof Multipart) {
extractMultipartContent((Multipart) msgContent, content);
}
return content.toString();
}
private void extractMultipartContent(Multipart mp, StringBuilder content)
throws MessagingException, IOException {
for (int i = 0; i < mp.getCount(); i++) {
BodyPart bp = mp.getBodyPart(i);
if (bp.isMimeType("text/plain") || bp.isMimeType("text/html")) {
content.append(bp.getContent().toString()).append(" ");
}
}
}
}
// Usage of custom search term
String[] keywords = {"project", "deadline", "budget"};
SearchTerm customSearch = new CustomSearchTerm(keywords, true); // Require all keywords
Message[] results = folder.search(customSearch);public class SearchOptimization {
public Message[] optimizedSearch(Folder folder) throws MessagingException {
// Strategy 1: Use server-side search when possible
// Most specific criteria first to reduce result set quickly
// Start with date range (usually most selective)
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MONTH, -1);
Date lastMonth = cal.getTime();
SearchTerm dateRange = new ReceivedDateTerm(ComparisonTerm.GT, lastMonth);
// Add flag criteria (often indexed on server)
SearchTerm unread = new FlagTerm(new Flags(Flags.Flag.SEEN), false);
// Combine with AND (server can optimize)
SearchTerm serverOptimized = new AndTerm(dateRange, unread);
// Get intermediate results
Message[] candidates = folder.search(serverOptimized);
// Apply more complex criteria client-side if needed
List<Message> finalResults = new ArrayList<>();
for (Message msg : candidates) {
if (complexContentMatch(msg)) {
finalResults.add(msg);
}
}
return finalResults.toArray(new Message[0]);
}
private boolean complexContentMatch(Message msg) {
// Perform expensive content analysis only on pre-filtered set
try {
Object content = msg.getContent();
// Complex analysis logic here
return true; // placeholder
} catch (Exception e) {
return false;
}
}
public void searchWithPagination(Folder folder, int pageSize) throws MessagingException {
// For large folders, paginate through results
SearchTerm criteria = new SubjectTerm("report");
int totalMessages = folder.getMessageCount();
for (int start = 1; start <= totalMessages; start += pageSize) {
int end = Math.min(start + pageSize - 1, totalMessages);
Message[] pageMessages = folder.getMessages(start, end);
Message[] pageResults = folder.search(criteria, pageMessages);
processResults(pageResults);
}
}
private void processResults(Message[] results) {
// Process page of results
System.out.println("Processing " + results.length + " messages");
}
}class SearchException extends MessagingException {
public SearchException();
public SearchException(String s);
}public Message[] safeSearch(Folder folder, SearchTerm term) {
try {
return folder.search(term);
} catch (SearchException e) {
System.err.println("Search failed: " + e.getMessage());
// Fallback to client-side filtering
return fallbackSearch(folder, term);
} catch (MessagingException e) {
System.err.println("Messaging error during search: " + e.getMessage());
return new Message[0];
}
}
private Message[] fallbackSearch(Folder folder, SearchTerm term) {
try {
// Get all messages and filter client-side
Message[] allMessages = folder.getMessages();
List<Message> results = new ArrayList<>();
for (Message msg : allMessages) {
try {
if (term.match(msg)) {
results.add(msg);
}
} catch (Exception e) {
// Skip problematic messages
continue;
}
}
return results.toArray(new Message[0]);
} catch (MessagingException e) {
System.err.println("Fallback search failed: " + e.getMessage());
return new Message[0];
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-sun-mail--jakarta-mail-api