Guide
Send Email in Java: Jakarta Mail and APIs
Four working ways to send email from Java: Jakarta Mail over SMTP, Spring Boot's spring-boot-starter-mail, the Gmail REST API client, and a ProcessBuilder call to a multi-provider CLI. Complete code for each plus a decision table.
Written by Aaron de Mello Senior Engineering Manager
Reviewed by Qasim Muhammad
Reference pages for the commands in this guide: nylas email send for the ProcessBuilder pattern, nylas auth login for the one-time OAuth flow, and nylas auth config for API-key auth in CI.
What are the ways to send email in Java?
To send email in Java you pick one of four methods: Jakarta Mail over SMTP, Spring Boot's spring-boot-starter-mail, a provider REST API such as the Gmail API, or a ProcessBuilder subprocess that calls a CLI binary. The JDK has never bundled an email API, so every option pulls in something external.
SMTP itself is defined in RFC 5321, published in October 2008, and Jakarta Mail implements it for the JVM. The table compares all four methods on setup time, auth model, and provider coverage so you can match the method to your project before writing a line of code.
| Method | Setup time | Auth | Providers | Dependencies |
|---|---|---|---|---|
| Jakarta Mail | ~10 minutes | App password / SMTP creds | Any SMTP server | angus-mail (1 artifact) |
| Spring Boot starter | ~5 minutes (in a Boot app) | App password / SMTP creds | Any SMTP server | spring-boot-starter-mail |
| Gmail REST API | ~20 minutes | OAuth 2.0 with scopes | Gmail only | google-api-services-gmail + auth libs |
| ProcessBuilder + CLI | ~2 minutes | OAuth 2.0 (browser flow) | Gmail, Outlook, Exchange, Yahoo, iCloud, IMAP | None (JDK only) |
A quick rule of thumb: pick Jakarta Mail for standalone JVM tools that talk to one SMTP host, the Spring starter when you're already inside a Boot application, the Gmail API when you need OAuth scopes narrower than full mailbox access, and the subprocess when you want multi-provider coverage with zero dependencies. The four sections below give complete, runnable code for each method, in that order.
How do I send email with Jakarta Mail?
Jakarta Mail is the standard Java email API, created as JavaMail in the late 1990s and transferred from Oracle to the Eclipse Foundation in 2017. Version 2.0 renamed every package from javax.mail to jakarta.mail, and the Jakarta Mail 2.1 specification ships in Jakarta EE 10 (2022). It speaks SMTP to any provider.
One Maven artifact gets you running: Eclipse Angus (org.eclipse.angus:angus-mail:2.0.3) implements the spec and pulls the API in transitively. The class below opens a STARTTLS connection to Gmail on port 587 and authenticates with a 16-character app password — the only password-style credential consumer Gmail has accepted since Less Secure Apps ended for personal accounts in May 2022 (the Workspace shutdown wrapped up on May 1, 2025).
// Maven: org.eclipse.angus:angus-mail:2.0.3
import jakarta.mail.*;
import jakarta.mail.internet.*;
import java.util.Properties;
public class SendMail {
public static void main(String[] args) throws MessagingException {
Properties props = new Properties();
props.put("mail.smtp.host", "smtp.gmail.com");
props.put("mail.smtp.port", "587");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
Session session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("you@gmail.com", "your-16-char-app-password");
}
});
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress("you@gmail.com"));
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse("recipient@example.com"));
message.setSubject("Quarterly report");
message.setText("See the attached spreadsheet.");
Transport.send(message);
}
}Gmail caps personal accounts at 500 messages per day and Workspace accounts at 2,000. The bigger maintenance cost is migration churn: code still importing javax.mail won't compile against the 2.x line, and the upgrade is a find-and-replace across every import in the codebase.
How do I send email with Spring Boot?
Spring Boot sends email through spring-boot-starter-mail, a starter that auto-configures a JavaMailSender bean from spring.mail.* properties. Jakarta Mail does the actual SMTP work underneath; the starter removes the Session and Authenticator boilerplate. Spring Boot 3, released in November 2022, requires Java 17 and uses the jakarta.mail namespace throughout.
Per the Spring Boot email reference, six properties configure the whole transport. The service below injects JavaMailSender through the constructor and sends a SimpleMailMessage in 5 lines of method body, with credentials kept out of the code.
/* src/main/resources/application.properties
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=you@gmail.com
spring.mail.password=your-16-char-app-password
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
*/
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
@Service
public class MailService {
private final JavaMailSender mailSender;
public MailService(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
public void send(String to, String subject, String body) {
SimpleMailMessage msg = new SimpleMailMessage();
msg.setFrom("you@gmail.com");
msg.setTo(to);
msg.setSubject(subject);
msg.setText(body);
mailSender.send(msg);
}
}This is the right pattern when email is one feature inside an existing Boot application: the bean is testable, the properties move between environments, and switching SMTP hosts is a config change. Outside a Spring context the starter buys you nothing — plain Jakarta Mail is lighter for a standalone tool.
How do I send email with the Gmail REST API in Java?
The Gmail API sends mail over HTTPS instead of SMTP, using the google-api-services-gmail Java client and an OAuth 2.0 token scoped to gmail.send. Each messages.send call costs 100 quota units against a per-user budget of 6,000 units per minute on projects created after May 1, 2026 (older projects keep the legacy 250 units per second), and access tokens expire after 3,600 seconds.
Per Google's sending guide, you build a raw RFC 2822 message, base64url-encode it, and pass it to users().messages().send(). The snippet assumes a Gmail service object from Google's Java quickstart OAuth flow; one wrong character in the encoding step and the API returns a 400.
import com.google.api.services.gmail.Gmail;
import com.google.api.services.gmail.model.Message;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
// "gmail" is an authorized Gmail service from Google's Java quickstart
String raw = String.join("\r\n",
"To: recipient@example.com",
"Subject: Quarterly report",
"Content-Type: text/plain; charset=utf-8",
"",
"See the attached spreadsheet.");
String encoded = Base64.getUrlEncoder().withoutPadding()
.encodeToString(raw.getBytes(StandardCharsets.UTF_8));
Message sent = gmail.users().messages()
.send("me", new Message().setRaw(encoded))
.execute();
System.out.println("Sent: " + sent.getId());The payoff is scoped access: an OAuth consent for gmail.send can't read the inbox, which app passwords can. The cost is lock-in. Outlook needs a different SDK entirely (Microsoft Graph sendMail), so multi-provider apps end up maintaining two parallel client stacks.
How do I send email from Java with ProcessBuilder?
ProcessBuilder, in the JDK since Java 5 (2004), runs an external binary and captures its output — which lets Java delegate email entirely to a CLI that already handles OAuth refresh, MIME encoding, and provider differences. No Maven dependency, no SMTP properties, no credentials in code. The Nylas CLI covers Gmail, Outlook, Exchange, Yahoo, iCloud, and IMAP from one command.
Setup takes about 2 minutes: install the binary with Homebrew and run nylas auth login once to complete the browser OAuth flow. Other install methods are listed in the getting started guide.
brew install nylas/nylas-cli/nylas
nylas auth loginThe nylas email send command does the sending; --json returns structured output for parsing and --yes skips the interactive confirmation so the subprocess never blocks on a prompt. The whole Java side is about 12 lines, and the same code works unchanged when the account behind the grant switches from Gmail to Outlook.
import java.io.IOException;
public class SendViaCli {
public static void main(String[] args) throws IOException, InterruptedException {
Process process = new ProcessBuilder(
"nylas", "email", "send",
"--to", "recipient@example.com",
"--subject", "Quarterly report",
"--body", "See the attached spreadsheet.",
"--json", "--yes")
.redirectErrorStream(true)
.start();
String output = new String(process.getInputStream().readAllBytes());
if (process.waitFor() != 0) {
throw new IOException("nylas email send failed: " + output);
}
System.out.println(output); // JSON with the sent message id
}
}Forking a process from the JVM costs roughly 100ms per send, far more than reusing a pooled Jakarta Mail Transport over an open SMTP connection, so batch jobs over 1,000 messages should stay on SMTP. For cron jobs, build notifications, and prototypes, the subprocess is the fastest of the four methods to ship. The same pattern works in Node.js and Python.
Next steps
- Send Email in Go: net/smtp, APIs, and CLI — Four ways to send email from Go
- Send Email in Rust: lettre and Email APIs — Three ways to send email in Rust
- Send email from Node.js — the same method comparison with Nodemailer in Jakarta Mail's role
- Send email from Python — smtplib, Gmail API, and subprocess patterns for Python scripts
- Build a Spring AI email agent — give a Spring application an email tool layer
- Twilio vs Nylas — how a transactional email API compares to a mailbox API
- EmailEngine vs Nylas — self-hosted vs managed email infrastructure
- Full command reference — every flag and subcommand documented