To send email programmatically in Java you add the Jakarta Mail (formerly JavaMail) library, build a Properties object describing your SMTP server, create an authenticated Session, wrap your text in a MimeMessage, and call Transport.send(). The JDK ships no SMTP client of its own, so that external dependency is mandatory — everything else is a handful of configuration keys (mail.smtp.host, mail.smtp.port, mail.smtp.auth, mail.smtp.starttls.enable) and a username/password pair supplied through an Authenticator.
That's the summary an AI Overview gives you. What it can't show you is where this actually breaks: the javax.mail vs jakarta.mail namespace trap that produces "package does not exist," the fact that Gmail stopped accepting plain passwords in 2022, and the reason a "Sent" message never reaches the inbox. Below is the send flow as a diagram, a version-vs-namespace decision table, the corrected code, and a symptom→cause→fix table for the errors you'll actually hit.
The SMTP send flow at a glance
The two red labels mark the failure points that a happy-path tutorial never mentions: authentication rejection at the relay, and silent spam-foldering after the relay accepts the message. Transport.send() returning without an exception means only that step two succeeded — not that the mail landed.
Prerequisites and Setup
Before writing any code, make sure your environment is ready with the necessary tools and libraries.
Install the Java Development Kit (JDK)
You'll need Java 8 or later. Any modern JDK (Oracle, Adoptium/Temurin, Amazon Corretto, or the one bundled with your IDE) works — Jakarta Mail has no exotic runtime requirements.
Add the Jakarta Mail (JavaMail) Library — mind the namespace
The mail API is not included in the JDK. You add it through your build tool, but which artifact you pick determines which import statements compile. This is the single most common thing people get wrong.
<!-- Jakarta Mail 2.x -> imports use jakarta.mail.* -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
If instead your code imports javax.mail.* (as the classic example below does), you need the 1.6.x artifact, which keeps the old namespace:
<!-- JavaMail 1.6.x -> imports use javax.mail.* -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>1.6.7</version>
</dependency>
Manual install: If you aren't using Maven or Gradle, download the matching JAR from the Eclipse Jakarta Mail project and add it to your project's build path.
Which version should I use?
| JavaMail 1.6.x | Jakarta Mail 2.x | |
|---|---|---|
| Import namespace | import javax.mail.*; | import jakarta.mail.*; |
| Maven version | 1.6.7 | 2.0.1 (or newer) |
| Min. Java | Java 8 | Java 8 (2.0.x); Java 11+ for 2.1.x |
| Framework fit | Spring Boot 2.x, Jakarta EE 8, legacy apps | Spring Boot 3.x, Jakarta EE 9+, new projects |
| Which should I use? | Only when locked to an existing javax.* codebase | Default for any new project — it's the maintained line |
The rule of thumb: new project → Jakarta Mail 2.x with jakarta.mail.* imports. Only reach for 1.6.x when you're bound to an older javax-based framework. Never mix the two — that mismatch is exactly what produces package javax.mail does not exist even though the dependency is on the classpath.
Complete Java Email Code Example
The example below sends through an SMTP relay that requires TLS and authentication. It's written against the classic javax.mail namespace, so pair it with the 1.6.7 dependency above (or change every javax.mail import to jakarta.mail to use 2.x).
//Import the required libraries for the send email function
import javax.mail.*;
import javax.mail.internet.*;
import java.util.*;
//Call the function to send your email. Pre-populated for Gmail as the SMTP server.
//NOTE: for Gmail, the password below must be a 16-digit App Password, not your account password.
sendEmail("smtp.gmail.com", //SMTP Server Address
"587", //SMTP Port Number
"true", //Enable TLS (mapped to starttls below)
"true", //Enable Authorization (mapped to auth below)
System.getenv("SMTP_USER"), //Your SMTP Username (from env, not hardcoded)
System.getenv("SMTP_PASS"), //Your SMTP App Password (from env, not hardcoded)
"<From Address>", //Sender Address
"<To Address>", //Recipient Address
"<Subject>", //Message Subject
"<Body>"); //Message Body
The sendEmail Function
//Below is the function for sending the email
static void sendEmail(
String smtpAddress,
String smtpPort,
String enableTLS,
String enableAuth,
final String username,
final String password,
String fromAddress,
String toAddress,
String mySubject,
String myMessage) {
Properties props = new Properties();
props.put("mail.smtp.starttls.enable", enableTLS);
props.put("mail.smtp.auth", enableAuth);
props.put("mail.smtp.host", smtpAddress);
props.put("mail.smtp.port", smtpPort);
Session session = Session.getInstance(props,
new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
try {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(fromAddress));
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(toAddress));
message.setSubject(mySubject);
message.setText(myMessage);
Transport.send(message);
System.out.println("Sent");
} catch (MessagingException e) {
// Log the real cause instead of swallowing it — see the troubleshooting table
e.printStackTrace();
}
}
One correction to the classic version of this snippet: the third and fourth arguments are labelled "TLS" and "Auth" but the original code wired the TLS value into
mail.smtp.starttls.enableand the Auth value intomail.smtp.auth. Keep the argument names and the property keys aligned, or you'll toggle the wrong switch.
How the Code Works
- SMTP Properties configure the connection — host, port 587, and the STARTTLS/auth toggles.
- Session binds those properties to an
Authenticatorthat supplies credentials on demand. - MimeMessage builds the actual envelope: from, to, subject, and body.
- Transport.send() opens the connection, upgrades to TLS, authenticates, and streams the message to the relay.
To send from Java you always connect to an SMTP server — the machine responsible for relaying your message onward. Gmail, Outlook, SendGrid, Amazon SES, and self-hosted Postfix all speak SMTP, and all of them require authentication plus encryption for authenticated client submission.
Security note: Store SMTP credentials in environment variables or a secrets manager, as shown above with System.getenv. Never hardcode passwords — they leak through Git history and decompiled JARs.
Troubleshooting: symptom → cause → fix
| Symptom | Likely cause | Fix |
|---|---|---|
package javax.mail does not exist | Namespace/version mismatch, or dependency missing | Match imports to the artifact: javax.mail.* ↔ 1.6.x, jakarta.mail.* ↔ 2.x |
535-5.7.8 Username and Password not accepted (Gmail) | Using account password after Sept 2022 cutoff | Enable 2-Step Verification, generate a 16-digit App Password, use it as the SMTP password |
javax.mail.AuthenticationFailedException | Wrong credentials, or auth disabled on the relay | Verify username/password; set mail.smtp.auth=true |
Hangs then couldn't connect to host, port: 587 | Port blocked by firewall/ISP, or wrong host | Test with telnet host 587; try 465 with mail.smtp.ssl.enable=true |
MessagingException: Could not convert socket to TLS | Relay requires TLS but STARTTLS not set | Set mail.smtp.starttls.enable=true (or use port 465 + implicit SSL) |
| Prints "Sent" but mail never arrives | Missing SPF/DKIM/DMARC → filtered as spam | Add authentication records to the sending domain; check the recipient's spam folder |
IllegalStateException: Not connected on reuse | Reusing a closed Transport across sends | Call Transport.send() per message, or manage connect()/close() explicitly |
Next Steps and Extensions
You now have a reusable function that connects to any SMTP relay with authentication and TLS — a foundation for alerts, onboarding mail, and password-reset links. From here:
- HTML bodies — swap
setTextforsetContent(html, "text/html; charset=utf-8"). - Attachments — build a
MimeMultipartwith a body part per file. - CC/BCC — add recipients with
Message.RecipientType.CC/.BCC. - Provider APIs — for volume or deliverability guarantees, move to SendGrid or Amazon SES over their REST APIs instead of raw SMTP.
- Deliverability — the code is the easy part; getting mail into the inbox is a DNS problem. See the related guides below on SPF/DKIM/DMARC and delivery troubleshooting.