Receiving: Store, Folder, Message, and flags
◈ 6 cardsConnect a Store, open INBOX read-only, filter on the SEEN flag, and handle a getContent that returns a Multipart — the two-invocation list-then-fetch program the assignment asks for.
Store, Folder, Message
Sending went through Transport. Reading goes through Store, from the same Session object — the only thing that changes is which protocol you ask for:
Store store = session.getStore("imaps");
store.connect(host, user, appPassword);
Folder inbox = store.getFolder("INBOX");
inbox.open(Folder.READ_ONLY);
The protocol names are imap (port 143), imaps (993, TLS from the first byte), pop3 (110) and pop3s (995); you can also set mail.store.protocol and call getStore() with no argument. connect is the line that touches the network — getStore does not.
"INBOX" is the one folder name guaranteed to exist: RFC 3501 reserves it, case-insensitively, on every IMAP server. Over POP3 it is also the only folder, which is one more reason the assignment's listing program wants IMAP.
open takes a mode, and the choice has consequences beyond permissions — see the pitfall below. getMessages() then returns a Message[].
Finding the unread ones
A Message returned by getMessages() is a handle, not a downloaded message: the first getter you call is what triggers a fetch. That laziness is a gift on a 5,000-message mailbox and a trap inside a loop.
So filter on the server, not in your loop. IMAP can evaluate a search itself, and Jakarta Mail exposes that through Folder.search(SearchTerm):
Message[] unread = inbox.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false));
The false means "flag not set", so this reads as messages without SEEN. One round trip, and the answer is computed where the mailbox lives. The client-side equivalent, msg.isSet(Flags.Flag.SEEN), gives the same answer for a much larger price.
The standard flags are SEEN, ANSWERED, FLAGGED, DELETED, DRAFT and RECENT. POP3 supports essentially none of them — a POP3 client fakes "unread" by remembering locally which message ids it has already downloaded, which is exactly why two POP3 devices disagree.
Reading a message you fetched
getFrom() returns an Address[] that can be null, so a real program checks before indexing. Cast the first element to InternetAddress to reach getAddress() (the mailbox) and getPersonal() (the display name).
getSubject() returns a decoded String — Jakarta Mail undoes the encoded-word escaping that lets a non-ASCII subject travel through a 7-bit header, so you get the real characters back.
getContent() is the one that surprises people. It is declared to return Object and it genuinely varies: a String for text/plain, a Multipart for anything with an attachment, an InputStream when no handler is registered for the type. This is the previous lesson viewed from the receiving end — the tree you built with MimeMultipart is the tree you get back. Test with instanceof, and when it is a Multipart, walk getCount() and getBodyPart(i) and display the first text/plain part.
Closing, and what expunge means
inbox.close(false) then store.close(). The boolean is expunge: true permanently removes every message currently flagged DELETED and renumbers what is left; false leaves them flagged and present. IMAP deletion is two steps on purpose — flag now, expunge later — so false is the safe default for anything that is not deliberately deleting mail.
Close both. Providers cap simultaneous IMAP connections per account, and a program that leaks one per run starts failing at connect after a handful of tests, which looks like a credential problem and is not.
Worked example — one program, two invocations
The assignment asks for a program whose behaviour depends on its argument count:
java GetMail imap.example.net dana@example.org app-password
1. Reading week schedule (registrar@example.net)
2. Re: lab notes (sam@example.net)
3. Birthday party this Friday (lee@example.net)
java GetMail imap.example.net dana@example.org app-password 2
[prints message 2]
Three arguments means list; four means fetch. The design question hiding in this is where the reference number comes from, because the process does not survive between the two invocations — nothing is remembered. The honest answer is that the number is an index into the unread list as computed by this connection, so the second run must rebuild the same list with the same filter before indexing into it:
Message[] unread = inbox.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false));
if (args.length == 3) {
for (int i = 0; i < unread.length; i++) {
InternetAddress from = (InternetAddress) unread[i].getFrom()[0];
System.out.printf("%d. %s (%s)%n", i + 1, unread[i].getSubject(), from.getAddress());
}
} else {
Message m = unread[Integer.parseInt(args[3]) - 1];
System.out.println(m.getSubject());
}
That is correct for the assignment and worth naming as a limitation in your test plan: new mail arriving between the two runs shifts every number. The durable fix is IMAP's UID, reachable by casting the folder to UIDFolder — an identifier that survives across connections precisely because sequence numbers do not. Naming that trade-off is exactly the reflection the notebook is marked on.