POP3/IMAP Email Reader in Java

Overview

A comprehensive email reader implementation supporting both POP3 and IMAP protocols with features for reading, searching, and processing emails.

Dependencies

Maven Dependencies

<dependencies>
<!-- JavaMail API -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
<!-- Apache Commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
</dependencies>

Core Implementation

1. Configuration Classes

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.Properties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class EmailConfig {
private String protocol; // "pop3", "imap", "imaps"
private String host;
private int port;
private String username;
private String password;
private boolean useSSL;
private boolean useTLS;
private int connectionTimeout = 30000;
private int readTimeout = 30000;
private int batchSize = 50;
// Default configurations for common providers
public static EmailConfig gmailImapConfig(String username, String password) {
EmailConfig config = new EmailConfig();
config.setProtocol("imaps");
config.setHost("imap.gmail.com");
config.setPort(993);
config.setUsername(username);
config.setPassword(password);
config.setUseSSL(true);
config.setUseTLS(true);
return config;
}
public static EmailConfig outlookImapConfig(String username, String password) {
EmailConfig config = new EmailConfig();
config.setProtocol("imaps");
config.setHost("outlook.office365.com");
config.setPort(993);
config.setUsername(username);
config.setPassword(password);
config.setUseSSL(true);
config.setUseTLS(true);
return config;
}
public static EmailConfig yahooPop3Config(String username, String password) {
EmailConfig config = new EmailConfig();
config.setProtocol("pop3");
config.setHost("pop.mail.yahoo.com");
config.setPort(995);
config.setUsername(username);
config.setPassword(password);
config.setUseSSL(true);
return config;
}
public Properties toJavaMailProperties() {
Properties props = new Properties();
// Set protocol-specific properties
String protocol = this.protocol.toLowerCase();
props.put("mail." + protocol + ".host", host);
props.put("mail." + protocol + ".port", String.valueOf(port));
// Authentication
props.put("mail." + protocol + ".auth", "true");
// SSL/TLS configuration
if (useSSL) {
props.put("mail." + protocol + ".ssl.enable", "true");
props.put("mail." + protocol + ".ssl.trust", host);
}
if (useTLS) {
props.put("mail." + protocol + ".starttls.enable", "true");
}
// Timeout settings
props.put("mail." + protocol + ".connectiontimeout", String.valueOf(connectionTimeout));
props.put("mail." + protocol + ".timeout", String.valueOf(readTimeout));
// IMAP-specific properties
if (protocol.startsWith("imap")) {
props.put("mail.imap.partialfetch", "false");
props.put("mail.imap.fetchsize", "8192");
props.put("mail.imap.peek", "true"); // Don't set seen flag when reading
}
// POP3-specific properties
if (protocol.startsWith("pop3")) {
props.put("mail.pop3.rsetbeforequit", "true");
}
return props;
}
// Getters and setters
public String getProtocol() { return protocol; }
public void setProtocol(String protocol) { this.protocol = protocol; }
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public boolean isUseSSL() { return useSSL; }
public void setUseSSL(boolean useSSL) { this.useSSL = useSSL; }
public boolean isUseTLS() { return useTLS; }
public void setUseTLS(boolean useTLS) { this.useTLS = useTLS; }
public int getConnectionTimeout() { return connectionTimeout; }
public void setConnectionTimeout(int connectionTimeout) { 
this.connectionTimeout = connectionTimeout; 
}
public int getReadTimeout() { return readTimeout; }
public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; }
public int getBatchSize() { return batchSize; }
public void setBatchSize(int batchSize) { this.batchSize = batchSize; }
@Override
public String toString() {
return String.format(
"EmailConfig{protocol='%s', host='%s', port=%d, username='%s', useSSL=%s, useTLS=%s}",
protocol, host, port, username, useSSL, useTLS
);
}
}

2. Email Message Model

import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;
public class EmailMessage {
private String messageId;
private String subject;
private String from;
private List<String> to;
private List<String> cc;
private List<String> bcc;
private Date sentDate;
private Date receivedDate;
private String plainText;
private String htmlText;
private List<EmailAttachment> attachments;
private Map<String, String> headers;
private int messageNumber;
private long size;
private boolean seen;
private boolean replied;
private boolean deleted;
private List<String> flags;
public EmailMessage() {
this.to = new ArrayList<>();
this.cc = new ArrayList<>();
this.bcc = new ArrayList<>();
this.attachments = new ArrayList<>();
this.headers = new HashMap<>();
this.flags = new ArrayList<>();
}
public static EmailMessage fromMimeMessage(Message message) throws EmailProcessingException {
EmailMessage email = new EmailMessage();
try {
// Basic message info
email.setMessageNumber(message.getMessageNumber());
email.setMessageId(getMessageId(message));
email.setSubject(decodeText(message.getSubject()));
email.setSentDate(message.getSentDate());
email.setReceivedDate(message.getReceivedDate());
email.setSize(message.getSize());
// Address information
email.setFrom(extractAddress(message.getFrom()));
email.setTo(extractAddresses(message.getRecipients(Message.RecipientType.TO)));
email.setCc(extractAddresses(message.getRecipients(Message.RecipientType.CC)));
email.setBcc(extractAddresses(message.getRecipients(Message.RecipientType.BCC)));
// Flags
email.setSeen(message.isSet(Flags.Flag.SEEN));
email.setReplied(message.isSet(Flags.Flag.ANSWERED));
email.setDeleted(message.isSet(Flags.Flag.DELETED));
email.setFlags(extractFlags(message.getFlags()));
// Headers
email.setHeaders(extractHeaders(message));
// Content
extractContent(message, email);
} catch (Exception e) {
throw new EmailProcessingException("Failed to parse email message", e);
}
return email;
}
private static void extractContent(Part part, EmailMessage email) 
throws MessagingException, IOException {
Object content = part.getContent();
if (content instanceof String) {
// Simple text content
String text = (String) content;
if (part.isMimeType("text/plain")) {
email.setPlainText(text);
} else if (part.isMimeType("text/html")) {
email.setHtmlText(text);
}
} else if (content instanceof Multipart) {
// Multipart content
Multipart multipart = (Multipart) content;
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
// Check for attachments
if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition()) ||
bodyPart.getFileName() != null) {
EmailAttachment attachment = extractAttachment(bodyPart);
if (attachment != null) {
email.getAttachments().add(attachment);
}
} else {
// Recursively process nested parts
extractContent(bodyPart, email);
}
}
}
}
private static EmailAttachment extractAttachment(BodyPart bodyPart) 
throws MessagingException, IOException {
String fileName = bodyPart.getFileName();
if (fileName == null) return null;
fileName = decodeText(fileName);
EmailAttachment attachment = new EmailAttachment();
attachment.setFileName(fileName);
attachment.setContentType(bodyPart.getContentType());
attachment.setSize(bodyPart.getSize());
// For large attachments, we might want to stream instead of loading into memory
if (bodyPart.getSize() < 10 * 1024 * 1024) { // 10MB limit
attachment.setData(bodyPart.getInputStream().readAllBytes());
}
return attachment;
}
private static String extractAddress(Address[] addresses) {
if (addresses == null || addresses.length == 0) return null;
return decodeText(addresses[0].toString());
}
private static List<String> extractAddresses(Address[] addresses) {
List<String> result = new ArrayList<>();
if (addresses != null) {
for (Address address : addresses) {
result.add(decodeText(address.toString()));
}
}
return result;
}
private static List<String> extractFlags(Flags flags) {
List<String> result = new ArrayList<>();
if (flags != null) {
Flags.Flag[] systemFlags = flags.getSystemFlags();
for (Flags.Flag flag : systemFlags) {
if (flag == Flags.Flag.ANSWERED) result.add("ANSWERED");
else if (flag == Flags.Flag.DELETED) result.add("DELETED");
else if (flag == Flags.Flag.DRAFT) result.add("DRAFT");
else if (flag == Flags.Flag.FLAGGED) result.add("FLAGGED");
else if (flag == Flags.Flag.RECENT) result.add("RECENT");
else if (flag == Flags.Flag.SEEN) result.add("SEEN");
}
String[] userFlags = flags.getUserFlags();
Collections.addAll(result, userFlags);
}
return result;
}
private static Map<String, String> extractHeaders(Message message) 
throws MessagingException {
Map<String, String> headers = new HashMap<>();
Enumeration<?> headerEnum = message.getAllHeaderLines();
while (headerEnum.hasMoreElements()) {
String header = (String) headerEnum.nextElement();
int colonIndex = header.indexOf(':');
if (colonIndex > 0) {
String key = header.substring(0, colonIndex).trim();
String value = header.substring(colonIndex + 1).trim();
headers.put(key, value);
}
}
return headers;
}
private static String getMessageId(Message message) throws MessagingException {
String[] messageIds = message.getHeader("Message-ID");
return (messageIds != null && messageIds.length > 0) ? messageIds[0] : null;
}
private static String decodeText(String text) {
if (text == null) return null;
try {
return MimeUtility.decodeText(MimeUtility.unfold(text));
} catch (Exception e) {
return text; // Return original if decoding fails
}
}
// Getters and setters
public String getMessageId() { return messageId; }
public void setMessageId(String messageId) { this.messageId = messageId; }
public String getSubject() { return subject; }
public void setSubject(String subject) { this.subject = subject; }
public String getFrom() { return from; }
public void setFrom(String from) { this.from = from; }
public List<String> getTo() { return to; }
public void setTo(List<String> to) { this.to = to; }
public List<String> getCc() { return cc; }
public void setCc(List<String> cc) { this.cc = cc; }
public List<String> getBcc() { return bcc; }
public void setBcc(List<String> bcc) { this.bcc = bcc; }
public Date getSentDate() { return sentDate; }
public void setSentDate(Date sentDate) { this.sentDate = sentDate; }
public Date getReceivedDate() { return receivedDate; }
public void setReceivedDate(Date receivedDate) { this.receivedDate = receivedDate; }
public String getPlainText() { return plainText; }
public void setPlainText(String plainText) { this.plainText = plainText; }
public String getHtmlText() { return htmlText; }
public void setHtmlText(String htmlText) { this.htmlText = htmlText; }
public List<EmailAttachment> getAttachments() { return attachments; }
public void setAttachments(List<EmailAttachment> attachments) { this.attachments = attachments; }
public Map<String, String> getHeaders() { return headers; }
public void setHeaders(Map<String, String> headers) { this.headers = headers; }
public int getMessageNumber() { return messageNumber; }
public void setMessageNumber(int messageNumber) { this.messageNumber = messageNumber; }
public long getSize() { return size; }
public void setSize(long size) { this.size = size; }
public boolean isSeen() { return seen; }
public void setSeen(boolean seen) { this.seen = seen; }
public boolean isReplied() { return replied; }
public void setReplied(boolean replied) { this.replied = replied; }
public boolean isDeleted() { return deleted; }
public void setDeleted(boolean deleted) { this.deleted = deleted; }
public List<String> getFlags() { return flags; }
public void setFlags(List<String> flags) { this.flags = flags; }
public String getTextContent() {
return plainText != null ? plainText : 
(htmlText != null ? stripHtml(htmlText) : null);
}
private String stripHtml(String html) {
return html.replaceAll("<[^>]+>", "").trim();
}
public boolean hasAttachments() {
return attachments != null && !attachments.isEmpty();
}
@Override
public String toString() {
return String.format(
"EmailMessage{from='%s', subject='%s', date=%s, attachments=%d}",
from, subject, sentDate, attachments.size()
);
}
}
public class EmailAttachment {
private String fileName;
private String contentType;
private byte[] data;
private long size;
// Getters and setters
public String getFileName() { return fileName; }
public void setFileName(String fileName) { this.fileName = fileName; }
public String getContentType() { return contentType; }
public void setContentType(String contentType) { this.contentType = contentType; }
public byte[] getData() { return data; }
public void setData(byte[] data) { this.data = data; }
public long getSize() { return size; }
public void setSize(long size) { this.size = size; }
public String getFileExtension() {
if (fileName == null) return "";
int dotIndex = fileName.lastIndexOf('.');
return dotIndex > 0 ? fileName.substring(dotIndex + 1).toLowerCase() : "";
}
public boolean isImage() {
return contentType != null && contentType.startsWith("image/");
}
public boolean isPdf() {
return "application/pdf".equals(contentType) || 
fileName.toLowerCase().endsWith(".pdf");
}
}

3. Email Reader Interface and Implementation

import javax.mail.*;
import javax.mail.search.SearchTerm;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public interface EmailReader {
// Connection management
void connect() throws EmailException;
void disconnect() throws EmailException;
boolean isConnected();
// Folder operations
List<String> getFolders() throws EmailException;
void openFolder(String folderName) throws EmailException;
void openFolder(String folderName, int mode) throws EmailException;
void closeFolder() throws EmailException;
// Email retrieval
List<EmailMessage> getMessages() throws EmailException;
List<EmailMessage> getMessages(int start, int end) throws EmailException;
EmailMessage getMessage(int messageNumber) throws EmailException;
List<EmailMessage> searchMessages(SearchTerm searchTerm) throws EmailException;
// Email operations
void markAsRead(int messageNumber) throws EmailException;
void markAsUnread(int messageNumber) throws EmailException;
void deleteMessage(int messageNumber) throws EmailException;
void moveMessage(int messageNumber, String targetFolder) throws EmailException;
// Batch operations
int getMessageCount() throws EmailException;
int getUnreadCount() throws EmailException;
// Async operations
CompletableFuture<List<EmailMessage>> getMessagesAsync();
CompletableFuture<Integer> getMessageCountAsync();
}
public class JavaMailEmailReader implements EmailReader {
private final EmailConfig config;
private Session session;
private Store store;
private Folder folder;
private final ExecutorService executor;
public JavaMailEmailReader(EmailConfig config) {
this.config = config;
this.executor = Executors.newCachedThreadPool();
}
@Override
public void connect() throws EmailException {
try {
Properties props = config.toJavaMailProperties();
session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(config.getUsername(), config.getPassword());
}
});
session.setDebug(false); // Set to true for debugging
store = session.getStore(config.getProtocol());
store.connect(config.getHost(), config.getPort(), 
config.getUsername(), config.getPassword());
} catch (Exception e) {
throw new EmailException("Failed to connect to email server", e);
}
}
@Override
public void disconnect() throws EmailException {
try {
if (folder != null && folder.isOpen()) {
folder.close(false);
}
if (store != null && store.isConnected()) {
store.close();
}
} catch (Exception e) {
throw new EmailException("Failed to disconnect from email server", e);
}
}
@Override
public boolean isConnected() {
return store != null && store.isConnected();
}
@Override
public List<String> getFolders() throws EmailException {
checkConnection();
try {
List<String> folderNames = new ArrayList<>();
Folder[] folders = store.getDefaultFolder().list();
for (Folder f : folders) {
folderNames.add(f.getName());
// Recursively get subfolders for IMAP
if (f.getType() == Folder.HOLDS_FOLDERS) {
addSubfolders(f, folderNames, "");
}
}
return folderNames;
} catch (Exception e) {
throw new EmailException("Failed to get folder list", e);
}
}
private void addSubfolders(Folder parent, List<String> folderNames, String prefix) 
throws MessagingException {
Folder[] subfolders = parent.list();
for (Folder subfolder : subfolders) {
String fullName = prefix + "/" + subfolder.getName();
folderNames.add(fullName);
if (subfolder.getType() == Folder.HOLDS_FOLDERS) {
addSubfolders(subfolder, folderNames, fullName);
}
}
}
@Override
public void openFolder(String folderName) throws EmailException {
openFolder(folderName, Folder.READ_ONLY);
}
@Override
public void openFolder(String folderName, int mode) throws EmailException {
checkConnection();
try {
if (folder != null && folder.isOpen()) {
folder.close(false);
}
folder = store.getFolder(folderName);
if (!folder.exists()) {
throw new EmailException("Folder does not exist: " + folderName);
}
folder.open(mode);
} catch (Exception e) {
throw new EmailException("Failed to open folder: " + folderName, e);
}
}
@Override
public void closeFolder() throws EmailException {
try {
if (folder != null && folder.isOpen()) {
folder.close(false);
}
folder = null;
} catch (Exception e) {
throw new EmailException("Failed to close folder", e);
}
}
@Override
public List<EmailMessage> getMessages() throws EmailException {
return getMessages(1, getMessageCount());
}
@Override
public List<EmailMessage> getMessages(int start, int end) throws EmailException {
checkFolderOpen();
try {
int totalMessages = folder.getMessageCount();
if (totalMessages == 0) {
return Collections.emptyList();
}
// Adjust bounds
start = Math.max(1, start);
end = Math.min(totalMessages, end);
List<EmailMessage> messages = new ArrayList<>();
Message[] javaMessages = folder.getMessages(start, end);
// Fetch envelope data first for better performance
folder.fetch(javaMessages, new FetchProfile[] {
FetchProfile.Item.ENVELOPE,
FetchProfile.Item.FLAGS,
FetchProfile.Item.CONTENT_INFO
});
for (Message javaMessage : javaMessages) {
try {
EmailMessage emailMessage = EmailMessage.fromMimeMessage(javaMessage);
messages.add(emailMessage);
} catch (EmailProcessingException e) {
System.err.println("Failed to process message " + 
javaMessage.getMessageNumber() + ": " + e.getMessage());
// Continue with next message
}
}
return messages;
} catch (Exception e) {
throw new EmailException("Failed to get messages", e);
}
}
@Override
public EmailMessage getMessage(int messageNumber) throws EmailException {
checkFolderOpen();
try {
Message javaMessage = folder.getMessage(messageNumber);
if (javaMessage == null) {
throw new EmailException("Message not found: " + messageNumber);
}
// Fetch full content
folder.fetch(new Message[]{javaMessage}, new FetchProfile[] {
FetchProfile.Item.ENVELOPE,
FetchProfile.Item.CONTENT_INFO,
FetchProfile.Item.FLAGS
});
return EmailMessage.fromMimeMessage(javaMessage);
} catch (Exception e) {
throw new EmailException("Failed to get message: " + messageNumber, e);
}
}
@Override
public List<EmailMessage> searchMessages(SearchTerm searchTerm) throws EmailException {
checkFolderOpen();
try {
Message[] javaMessages = folder.search(searchTerm);
List<EmailMessage> messages = new ArrayList<>();
for (Message javaMessage : javaMessages) {
try {
EmailMessage emailMessage = EmailMessage.fromMimeMessage(javaMessage);
messages.add(emailMessage);
} catch (EmailProcessingException e) {
System.err.println("Failed to process search result: " + e.getMessage());
}
}
return messages;
} catch (Exception e) {
throw new EmailException("Failed to search messages", e);
}
}
@Override
public void markAsRead(int messageNumber) throws EmailException {
setMessageFlags(messageNumber, new Flags(Flags.Flag.SEEN), true);
}
@Override
public void markAsUnread(int messageNumber) throws EmailException {
setMessageFlags(messageNumber, new Flags(Flags.Flag.SEEN), false);
}
@Override
public void deleteMessage(int messageNumber) throws EmailException {
setMessageFlags(messageNumber, new Flags(Flags.Flag.DELETED), true);
}
@Override
public void moveMessage(int messageNumber, String targetFolder) throws EmailException {
checkFolderOpen();
try {
Message message = folder.getMessage(messageNumber);
if (message == null) {
throw new EmailException("Message not found: " + messageNumber);
}
Folder target = store.getFolder(targetFolder);
if (!target.exists()) {
throw new EmailException("Target folder does not exist: " + targetFolder);
}
folder.copyMessages(new Message[]{message}, target);
} catch (Exception e) {
throw new EmailException("Failed to move message to: " + targetFolder, e);
}
}
@Override
public int getMessageCount() throws EmailException {
checkFolderOpen();
try {
return folder.getMessageCount();
} catch (Exception e) {
throw new EmailException("Failed to get message count", e);
}
}
@Override
public int getUnreadCount() throws EmailException {
checkFolderOpen();
try {
return folder.getUnreadMessageCount();
} catch (Exception e) {
throw new EmailException("Failed to get unread count", e);
}
}
@Override
public CompletableFuture<List<EmailMessage>> getMessagesAsync() {
return CompletableFuture.supplyAsync(() -> {
try {
return getMessages();
} catch (EmailException e) {
throw new RuntimeException(e);
}
}, executor);
}
@Override
public CompletableFuture<Integer> getMessageCountAsync() {
return CompletableFuture.supplyAsync(() -> {
try {
return getMessageCount();
} catch (EmailException e) {
throw new RuntimeException(e);
}
}, executor);
}
private void setMessageFlags(int messageNumber, Flags flags, boolean set) 
throws EmailException {
checkFolderOpen();
try {
Message message = folder.getMessage(messageNumber);
if (message == null) {
throw new EmailException("Message not found: " + messageNumber);
}
message.setFlags(flags, set);
} catch (Exception e) {
throw new EmailException("Failed to set message flags", e);
}
}
private void checkConnection() throws EmailException {
if (!isConnected()) {
throw new EmailException("Not connected to email server");
}
}
private void checkFolderOpen() throws EmailException {
checkConnection();
if (folder == null || !folder.isOpen()) {
throw new EmailException("No folder open");
}
}
public void setSessionDebug(boolean debug) {
if (session != null) {
session.setDebug(debug);
}
}
@Override
protected void finalize() throws Throwable {
try {
disconnect();
executor.shutdown();
} finally {
super.finalize();
}
}
}

4. Search Utilities

import javax.mail.search.*;
public class EmailSearchBuilder {
public static SearchTerm from(String fromAddress) {
return new FromStringTerm(fromAddress);
}
public static SearchTerm subjectContains(String text) {
return new SubjectTerm(text);
}
public static SearchTerm bodyContains(String text) {
return new BodyTerm(text);
}
public static SearchTerm recipientContains(String address, Message.RecipientType type) {
return new RecipientStringTerm(type, address);
}
public static SearchTerm hasAttachment() {
return new FlagTerm(new Flags(Flags.Flag.FLAGGED), false); // Custom implementation needed
}
public static SearchTerm isUnread() {
return new FlagTerm(new Flags(Flags.Flag.SEEN), false);
}
public static SearchTerm isRead() {
return new FlagTerm(new Flags(Flags.Flag.SEEN), true);
}
public static SearchTerm isFlagged() {
return new FlagTerm(new Flags(Flags.Flag.FLAGGED), true);
}
public static SearchTerm receivedAfter(Date date) {
return new ReceivedDateTerm(ComparisonTerm.GT, date);
}
public static SearchTerm receivedBefore(Date date) {
return new ReceivedDateTerm(ComparisonTerm.LT, date);
}
public static SearchTerm sentAfter(Date date) {
return new SentDateTerm(ComparisonTerm.GT, date);
}
public static SearchTerm sentBefore(Date date) {
return new SentDateTerm(ComparisonTerm.LT, date);
}
public static SearchTerm and(SearchTerm... terms) {
return new AndTerm(terms);
}
public static SearchTerm or(SearchTerm... terms) {
return new OrTerm(terms);
}
public static SearchTerm not(SearchTerm term) {
return new NotTerm(term);
}
// Complex search examples
public static SearchTerm unreadFromImportantSenders() {
return and(
isUnread(),
or(
from("[email protected]"),
from("[email protected]"),
subjectContains("URGENT"),
subjectContains("IMPORTANT")
)
);
}
public static SearchTerm recentPromotions() {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, -7); // Last 7 days
Date weekAgo = cal.getTime();
return and(
receivedAfter(weekAgo),
or(
from("@amazon.com"),
from("@ebay.com"),
subjectContains("sale"),
subjectContains("discount"),
subjectContains("promotion")
)
);
}
}

5. Exception Classes

public class EmailException extends Exception {
public EmailException(String message) {
super(message);
}
public EmailException(String message, Throwable cause) {
super(message, cause);
}
}
public class EmailProcessingException extends EmailException {
public EmailProcessingException(String message) {
super(message);
}
public EmailProcessingException(String message, Throwable cause) {
super(message, cause);
}
}

6. Advanced Email Client with Management Features

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class AdvancedEmailClient {
private final EmailReader emailReader;
private final EmailConfig config;
private final ObjectMapper objectMapper;
private final Map<String, List<EmailMessage>> cache;
private final EmailFilterManager filterManager;
public AdvancedEmailClient(EmailConfig config) throws EmailException {
this.config = config;
this.emailReader = new JavaMailEmailReader(config);
this.objectMapper = new ObjectMapper();
this.cache = new ConcurrentHashMap<>();
this.filterManager = new EmailFilterManager();
}
public void connect() throws EmailException {
emailReader.connect();
}
public void disconnect() throws EmailException {
emailReader.disconnect();
cache.clear();
}
public EmailStatistics getStatistics(String folderName) throws EmailException {
emailReader.openFolder(folderName);
int total = emailReader.getMessageCount();
int unread = emailReader.getUnreadCount();
List<EmailMessage> recentMessages = emailReader.getMessages(
Math.max(1, total - 100), total); // Last 100 messages
EmailStatistics stats = new EmailStatistics();
stats.setTotalMessages(total);
stats.setUnreadMessages(unread);
stats.setRecentMessageCount(recentMessages.size());
// Analyze senders
Map<String, Long> senderCounts = recentMessages.stream()
.filter(msg -> msg.getFrom() != null)
.collect(Collectors.groupingBy(EmailMessage::getFrom, Collectors.counting()));
stats.setTopSenders(senderCounts.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
// Analyze subjects for common topics
stats.setCommonTopics(analyzeTopics(recentMessages));
return stats;
}
public List<EmailMessage> getFilteredMessages(String folderName, EmailFilter filter) 
throws EmailException {
emailReader.openFolder(folderName);
List<EmailMessage> messages;
if (cache.containsKey(folderName)) {
messages = cache.get(folderName);
} else {
messages = emailReader.getMessages();
cache.put(folderName, messages);
}
return filterManager.applyFilter(messages, filter);
}
public void exportMessages(String folderName, String outputPath, ExportFormat format) 
throws EmailException, IOException {
emailReader.openFolder(folderName);
List<EmailMessage> messages = emailReader.getMessages();
switch (format) {
case JSON:
exportToJson(messages, outputPath);
break;
case CSV:
exportToCsv(messages, outputPath);
break;
case TEXT:
exportToText(messages, outputPath);
break;
}
}
public void processEmails(String folderName, EmailProcessor processor) throws EmailException {
emailReader.openFolder(folderName);
List<EmailMessage> messages = emailReader.getMessages();
for (EmailMessage message : messages) {
try {
processor.process(message);
} catch (Exception e) {
System.err.println("Failed to process message " + message.getMessageNumber() + 
": " + e.getMessage());
}
}
}
public CompletableFuture<Void> processEmailsAsync(String folderName, EmailProcessor processor) {
return CompletableFuture.runAsync(() -> {
try {
processEmails(folderName, processor);
} catch (EmailException e) {
throw new RuntimeException(e);
}
});
}
private void exportToJson(List<EmailMessage> messages, String outputPath) throws IOException {
List<Map<String, Object>> exportData = messages.stream()
.map(this::messageToMap)
.collect(Collectors.toList());
objectMapper.writerWithDefaultPrettyPrinter()
.writeValue(new File(outputPath), exportData);
}
private void exportToCsv(List<EmailMessage> messages, String outputPath) throws IOException {
// Implementation for CSV export
// Would use a CSV library like OpenCSV
}
private void exportToText(List<EmailMessage> messages, String outputPath) throws IOException {
StringBuilder sb = new StringBuilder();
for (EmailMessage message : messages) {
sb.append("From: ").append(message.getFrom()).append("\n");
sb.append("Subject: ").append(message.getSubject()).append("\n");
sb.append("Date: ").append(message.getSentDate()).append("\n");
sb.append("Body: ").append(message.getTextContent()).append("\n");
sb.append("---\n\n");
}
Files.write(Paths.get(outputPath), sb.toString().getBytes());
}
private Map<String, Object> messageToMap(EmailMessage message) {
Map<String, Object> map = new HashMap<>();
map.put("messageId", message.getMessageId());
map.put("from", message.getFrom());
map.put("subject", message.getSubject());
map.put("sentDate", message.getSentDate());
map.put("receivedDate", message.getReceivedDate());
map.put("hasAttachments", message.hasAttachments());
map.put("isRead", message.isSeen());
map.put("bodyPreview", message.getTextContent() != null ? 
message.getTextContent().substring(0, 
Math.min(200, message.getTextContent().length())) : "");
return map;
}
private Set<String> analyzeTopics(List<EmailMessage> messages) {
// Simple topic analysis based on common words in subjects
Map<String, Integer> wordCounts = new HashMap<>();
for (EmailMessage message : messages) {
if (message.getSubject() != null) {
String[] words = message.getSubject().toLowerCase().split("\\s+");
for (String word : words) {
if (word.length() > 3 && !isCommonWord(word)) {
wordCounts.put(word, wordCounts.getOrDefault(word, 0) + 1);
}
}
}
}
return wordCounts.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.limit(5)
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}
private boolean isCommonWord(String word) {
Set<String> commonWords = Set.of("the", "and", "for", "with", "this", "that", "your", "have");
return commonWords.contains(word);
}
// Getters
public EmailReader getEmailReader() { return emailReader; }
public EmailFilterManager getFilterManager() { return filterManager; }
}
// Supporting classes
public class EmailStatistics {
private int totalMessages;
private int unreadMessages;
private int recentMessageCount;
private Map<String, Long> topSenders;
private Set<String> commonTopics;
// Getters and setters
public int getTotalMessages() { return totalMessages; }
public void setTotalMessages(int totalMessages) { this.totalMessages = totalMessages; }
public int getUnreadMessages() { return unreadMessages; }
public void setUnreadMessages(int unreadMessages) { this.unreadMessages = unreadMessages; }
public int getRecentMessageCount() { return recentMessageCount; }
public void setRecentMessageCount(int recentMessageCount) { this.recentMessageCount = recentMessageCount; }
public Map<String, Long> getTopSenders() { return topSenders; }
public void setTopSenders(Map<String, Long> topSenders) { this.topSenders = topSenders; }
public Set<String> getCommonTopics() { return commonTopics; }
public void setCommonTopics(Set<String> commonTopics) { this.commonTopics = commonTopics; }
@Override
public String toString() {
return String.format(
"EmailStatistics{total=%d, unread=%d, recent=%d, topSenders=%d, topics=%s}",
totalMessages, unreadMessages, recentMessageCount, 
topSenders.size(), commonTopics
);
}
}
public class EmailFilter {
private String from;
private String subjectContains;
private String bodyContains;
private Boolean hasAttachments;
private Boolean isRead;
private Date receivedAfter;
private Date receivedBefore;
// Getters and setters
public String getFrom() { return from; }
public void setFrom(String from) { this.from = from; }
public String getSubjectContains() { return subjectContains; }
public void setSubjectContains(String subjectContains) { this.subjectContains = subjectContains; }
public String getBodyContains() { return bodyContains; }
public void setBodyContains(String bodyContains) { this.bodyContains = bodyContains; }
public Boolean getHasAttachments() { return hasAttachments; }
public void setHasAttachments(Boolean hasAttachments) { this.hasAttachments = hasAttachments; }
public Boolean getIsRead() { return isRead; }
public void setIsRead(Boolean isRead) { this.isRead = isRead; }
public Date getReceivedAfter() { return receivedAfter; }
public void setReceivedAfter(Date receivedAfter) { this.receivedAfter = receivedAfter; }
public Date getReceivedBefore() { return receivedBefore; }
public void setReceivedBefore(Date receivedBefore) { this.receivedBefore = receivedBefore; }
}
public class EmailFilterManager {
public List<EmailMessage> applyFilter(List<EmailMessage> messages, EmailFilter filter) {
return messages.stream()
.filter(msg -> filter.getFrom() == null || 
(msg.getFrom() != null && msg.getFrom().contains(filter.getFrom())))
.filter(msg -> filter.getSubjectContains() == null || 
(msg.getSubject() != null && msg.getSubject().toLowerCase()
.contains(filter.getSubjectContains().toLowerCase())))
.filter(msg -> filter.getBodyContains() == null || 
(msg.getTextContent() != null && msg.getTextContent().toLowerCase()
.contains(filter.getBodyContains().toLowerCase())))
.filter(msg -> filter.getHasAttachments() == null || 
msg.hasAttachments() == filter.getHasAttachments())
.filter(msg -> filter.getIsRead() == null || 
msg.isSeen() == filter.getIsRead())
.filter(msg -> filter.getReceivedAfter() == null || 
(msg.getReceivedDate() != null && 
!msg.getReceivedDate().before(filter.getReceivedAfter())))
.filter(msg -> filter.getReceivedBefore() == null || 
(msg.getReceivedDate() != null && 
!msg.getReceivedDate().after(filter.getReceivedBefore())))
.collect(Collectors.toList());
}
}
public interface EmailProcessor {
void process(EmailMessage message) throws Exception;
}
public enum ExportFormat {
JSON, CSV, TEXT
}

7. Usage Examples and Demo

import java.util.*;
import java.util.concurrent.CompletableFuture;
public class EmailReaderDemo {
public static void main(String[] args) {
try {
// Configuration for Gmail
EmailConfig config = EmailConfig.gmailImapConfig(
"[email protected]", 
"your-app-password" // Use app password, not regular password
);
AdvancedEmailClient client = new AdvancedEmailClient(config);
client.connect();
System.out.println("Connected to email server");
// Get folder list
List<String> folders = client.getEmailReader().getFolders();
System.out.println("Available folders: " + folders);
// Open INBOX
client.getEmailReader().openFolder("INBOX");
// Get statistics
EmailStatistics stats = client.getStatistics("INBOX");
System.out.println("Email Statistics: " + stats);
// Example 1: Read recent emails
System.out.println("\n--- Recent Emails ---");
List<EmailMessage> recentEmails = client.getEmailReader().getMessages(
Math.max(1, stats.getTotalMessages() - 10), stats.getTotalMessages());
for (EmailMessage email : recentEmails) {
System.out.printf("From: %s\nSubject: %s\nDate: %s\nUnread: %s\n\n",
email.getFrom(), email.getSubject(), email.getSentDate(), !email.isSeen());
}
// Example 2: Search for specific emails
System.out.println("--- Searching for Unread Emails ---");
SearchTerm unreadSearch = EmailSearchBuilder.isUnread();
List<EmailMessage> unreadEmails = client.getEmailReader().searchMessages(unreadSearch);
System.out.println("Found " + unreadEmails.size() + " unread emails");
// Example 3: Filter emails
System.out.println("\n--- Filtered Emails ---");
EmailFilter filter = new EmailFilter();
filter.setHasAttachments(true);
filter.setIsRead(false);
List<EmailMessage> filtered = client.getFilteredMessages("INBOX", filter);
System.out.println("Found " + filtered.size() + " unread emails with attachments");
// Example 4: Process emails with custom processor
System.out.println("\n--- Processing Emails ---");
client.processEmails("INBOX", new EmailProcessor() {
@Override
public void process(EmailMessage message) {
System.out.printf("Processing: %s - %s\n", 
message.getFrom(), message.getSubject());
if (message.hasAttachments()) {
System.out.println("  Has " + message.getAttachments().size() + " attachments");
}
}
});
// Example 5: Async processing
System.out.println("\n--- Async Processing ---");
CompletableFuture<Void> asyncProcess = client.processEmailsAsync("INBOX", message -> {
if (message.getSubject() != null && message.getSubject().contains("newsletter")) {
System.out.println("Newsletter: " + message.getSubject());
// Mark newsletter as read
client.getEmailReader().markAsRead(message.getMessageNumber());
}
});
asyncProcess.thenRun(() -> System.out.println("Async processing completed"));
// Wait for async completion
asyncProcess.get();
// Example 6: Export emails
System.out.println("\n--- Exporting Emails ---");
client.exportMessages("INBOX", "emails_export.json", ExportFormat.JSON);
System.out.println("Emails exported to emails_export.json");
client.disconnect();
System.out.println("Disconnected from email server");
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
}
}

Key Features

  1. Dual Protocol Support: POP3 and IMAP with SSL/TLS
  2. Comprehensive Email Processing: Full MIME message parsing
  3. Advanced Search: Built-in search utilities with complex query support
  4. Batch Operations: Efficient processing of multiple emails
  5. Async Support: Non-blocking email operations
  6. Export Capabilities: JSON, CSV, and text exports
  7. Filtering System: Flexible email filtering and categorization
  8. Statistics: Email analytics and insights
  9. Attachment Handling: Automatic attachment detection and processing
  10. Error Handling: Comprehensive exception handling and recovery

Security Notes

  • Use app passwords for Gmail instead of regular passwords
  • Enable 2-factor authentication for email accounts
  • Store credentials securely (not in code)
  • Use SSL/TLS connections for security
  • Consider implementing connection pooling for production use

This implementation provides a robust foundation for building email client applications, automation tools, or email processing systems in Java.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper