CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-sun-mail--jakarta-mail-api

Jakarta Mail API provides a platform-independent and protocol-independent framework to build mail and messaging applications

Pending
Overview
Eval results
Files

search-capabilities.mddocs/

Search Capabilities

This document covers the comprehensive message search functionality provided by the javax.mail.search package, including search terms, logical operators, and complex query construction.

Search Foundation

The SearchTerm class is the abstract base class for all search criteria in the Jakarta Mail API.

Base Search Term

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.

Comparison Base Class

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);
}

Usage Example

// 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");

Logical Operations

AND Operations

final class AndTerm extends SearchTerm {
    public AndTerm(SearchTerm t1, SearchTerm t2);
    public AndTerm(SearchTerm[] terms);
    
    public SearchTerm[] getTerms();
    public boolean match(Message msg);
}

OR Operations

final class OrTerm extends SearchTerm {
    public OrTerm(SearchTerm t1, SearchTerm t2);
    public OrTerm(SearchTerm[] terms);
    
    public SearchTerm[] getTerms();
    public boolean match(Message msg);
}

NOT Operations

final class NotTerm extends SearchTerm {
    public NotTerm(SearchTerm t);
    
    public SearchTerm getTerm();
    public boolean match(Message msg);
}

Logical Operations Example

// 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);

String-Based Searches

Base String Term

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);
}

Subject Searches

final class SubjectTerm extends StringTerm {
    public SubjectTerm(String pattern);
    
    public boolean match(Message msg);
}

Body Content Searches

final class BodyTerm extends StringTerm {
    public BodyTerm(String pattern);
    
    public boolean match(Message msg);
}

Header Searches

final class HeaderTerm extends StringTerm {
    protected String headerName;
    
    public HeaderTerm(String headerName, String pattern);
    
    public String getHeaderName();
    public boolean match(Message msg);
}

Message ID Searches

final class MessageIDTerm extends StringTerm {
    public MessageIDTerm(String pattern);
    
    public boolean match(Message msg);
}

String Search Examples

// 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")
);

Address-Based Searches

Base Address Term

abstract class AddressTerm extends SearchTerm {
    protected Address address;
    
    public AddressTerm(Address address);
    
    public Address getAddress();
    
    protected boolean match(Address a);
}

From Address Searches

final class FromTerm extends AddressTerm {
    public FromTerm(Address address);
    
    public boolean match(Message msg);
}

Recipient Searches

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);
}

String-Based Address Searches

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);
}

Address Search Examples

// 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@")
);

Date-Based Searches

Base Date Term

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);
}

Sent Date Searches

final class SentDateTerm extends DateTerm {
    public SentDateTerm(int comparison, Date date);
    
    public boolean match(Message msg);
}

Received Date Searches

final class ReceivedDateTerm extends DateTerm {
    public ReceivedDateTerm(int comparison, Date date);
    
    public boolean match(Message msg);
}

Date Search Examples

// 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)
);

Numeric Searches

Base Integer Comparison Term

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);
}

Message Number Searches

final class MessageNumberTerm extends IntegerComparisonTerm {
    public MessageNumberTerm(int comparison, int number);
    
    public boolean match(Message msg);
}

Size-Based Searches

final class SizeTerm extends IntegerComparisonTerm {
    public SizeTerm(int comparison, int size);
    
    public boolean match(Message msg);
}

Numeric Search Examples

// 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)
);

Flag-Based Searches

Flag Term Searches

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);
}

Flag Search Examples

// 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);

Complex Search Examples

Multi-Criteria Search

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
        }));
    }
}

Search with Custom Logic

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);

Search Performance Optimization

Efficient Search Strategies

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");
    }
}

Exception Handling

class SearchException extends MessagingException {
    public SearchException();
    public SearchException(String s);
}

Search Error Handling

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@1.6.1

docs

core-messaging.md

event-handling.md

folder-operations.md

index.md

internet-messaging.md

search-capabilities.md

tile.json