Source: https://cli.nylas.com/guides/send-email-java

# 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](https://cli.nylas.com/authors/aaron-de-mello) Senior Engineering Manager

Reviewed by [Qasim Muhammad](https://cli.nylas.com/authors/qasim-muhammad)

Updated June 9, 2026

> **TL;DR:** Jakarta Mail (the renamed JavaMail) sends over SMTP with one Maven dependency. Spring Boot wraps it in an injectable `JavaMailSender`. The Gmail REST API trades SMTP for OAuth-scoped HTTPS calls. One of the four methods below needs zero Maven dependencies and about 12 lines — the comparison table shows when the heavier options still win.

> **Disclosure:** Nylas CLI is built by Nylas, Inc. This comparison reflects our testing and product understanding as of June 9, 2026.

Reference pages for the commands in this guide: [`nylas email send`](https://cli.nylas.com/docs/commands/email-send) for the ProcessBuilder pattern, [`nylas auth login`](https://cli.nylas.com/docs/commands/auth-login) for the one-time OAuth flow, and [`nylas auth config`](https://cli.nylas.com/docs/commands/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](https://datatracker.ietf.org/doc/html/rfc5321), 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](https://jakarta.ee/specifications/mail/) ships in Jakarta EE 10 (2022). It speaks SMTP to any provider.

One Maven artifact gets you running: [Eclipse Angus](https://eclipse-ee4j.github.io/angus-mail/) (`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](https://support.google.com/accounts/answer/185833) — 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).

```java
// 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](https://docs.spring.io/spring-boot/reference/io/email.html), 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.

```java
/* 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](https://developers.google.com/workspace/gmail/api/guides/sending), 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.

```java
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](https://learn.microsoft.com/en-us/graph/api/user-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](https://cli.nylas.com/guides/getting-started).

```bash
brew install nylas/nylas-cli/nylas
nylas auth login
```

The `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.

```java
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](https://cli.nylas.com/guides/send-email-nodejs) and [Python](https://cli.nylas.com/guides/send-email-python).

## Next steps

- [Send Email in Go: net/smtp, APIs, and CLI](https://cli.nylas.com/guides/send-email-go) — Four ways to send email from Go
- [Send Email in Rust: lettre and Email APIs](https://cli.nylas.com/guides/send-email-rust) — Three ways to send email in Rust
- [Send email from Node.js](https://cli.nylas.com/guides/send-email-nodejs) — the same method comparison with Nodemailer in Jakarta Mail's role
- [Send email from Python](https://cli.nylas.com/guides/send-email-python) — smtplib, Gmail API, and subprocess patterns for Python scripts
- [Build a Spring AI email agent](https://cli.nylas.com/guides/spring-ai-email-agent) — give a Spring application an email tool layer
- [Twilio vs Nylas](https://cli.nylas.com/guides/twilio-vs-nylas) — how a transactional email API compares to a mailbox API
- [EmailEngine vs Nylas](https://cli.nylas.com/guides/emailengine-vs-nylas) — self-hosted vs managed email infrastructure
- [Full command reference](https://cli.nylas.com/docs/commands) — every flag and subcommand documented
