Migrating to Simple Java Mail 9.0.0
- Dedicated recipient builders
- Per-recipient S/MIME certificates
- Outlook conversion results expose source data
- Delivery Status Notification API
- Per-body Content-Transfer-Encoding defaults
- Pre-encoded attachments and embedded images
- Explicit resource Content-ID values
- Jakarta Mail debug output targets
- Local bind address configuration
- Mailer-level default DKIM signing
- Simple sequential batch and open-connection sending
- Batch-module cluster configuration
- Async mailer defaults apply to testConnection
- Resource filenames moved out of Content-Type filename
- Java module descriptors
Dedicated recipient builders
Impact: The old repeated to(...), cc(...) and
bcc(...) recipient method matrix has been replaced by dedicated recipient builders plus the canonical
withRecipients(...) methods on the email builder.
Build one recipient with RecipientBuilder or a group with RecipientsBuilder, then add the
result to the email using withRecipients(Recipient...) or
withRecipients(Collection<Recipient>). The string-based generic
withRecipients(name, fixedName, type, addresses...) method remains for CLI and property-driven integration code.
For grouped recipients, RecipientsBuilder is also the place to express local governance. Default names and certificates
fill missing values, fixed names and certificates intentionally replace source values for that group, and
clearingSmimeCertificates() strips reused recipient certificate state before the flat recipient list is added to the
email. After that, recipient certificates take precedence over email-level and mailer-level S/MIME encryption fallbacks.
Recipient recipient = new RecipientBuilder()
.withAddressAndNameOrDefault("Alice <alice@example.com>", null)
.withType(Message.RecipientType.TO)
.build();
Email email = EmailBuilder.startingBlank()
.from("me@example.com")
.withRecipients(recipient)
.withPlainText("Hello")
.buildEmail();
Per-recipient S/MIME certificates
Impact: Existing email-level and mailer-level S/MIME defaults still work. Use recipient certificates when recipients need different certificates.
Recipients can now carry their own S/MIME encryption certificate. Recipient-level certificates, including certificates materialized by
RecipientsBuilder group defaults or fixed group values, take precedence over governance-resolved email and mailer
encryption defaults. This makes multi-recipient encrypted email practical without one shared certificate source.
When reusing recipients, RecipientsBuilder.clearingSmimeCertificates() removes existing recipient certificate state so
the current email can fall back to its email-level or mailer-level S/MIME encryption configuration.
Recipient alice = new RecipientBuilder()
.withAddress("alice@example.com")
.withType(Message.RecipientType.TO)
.withSmimeCertificate(aliceCertificate)
.build();
Email email = EmailBuilder.startingBlank()
.from("me@example.com")
.withRecipients(alice)
.withPlainText("Encrypted for Alice")
.buildEmail();
Outlook conversion results expose source data
Impact: Code using the old direct Outlook-to-email conversion methods can keep doing so. Use the new result API when source
.msg metadata is needed.
Outlook conversion now has an explicit result API that separates the converted email builder from Outlook-specific source data. Structural source headers and MAPI metadata no longer need to be copied into the converted email just to remain inspectable.
OutlookEmailConversionResult result =
EmailConverter.outlookMsgToEmailBuilderWithOutlookData(msgFile);
Email email = result.buildEmail();
OutlookMessageData outlookData = result.getOutlookMessageData();
List<String> receivedHeaders = outlookData.getHeaderValues("Received");
The old InputStream overloads returning EmailFromOutlookMessage are deprecated in favor
of outlookMsgToEmailBuilderWithOutlookData(...).
Delivery Status Notification API
Impact: New API only. Projects can now request SMTP DSN behavior through Java, property files and Spring configuration.
Delivery Status Notification settings can be attached to an email using
DeliveryStatusNotification and the new email builder methods.
Email email = EmailBuilder.startingBlank()
.from("sender@example.com")
.withRecipients(new RecipientBuilder()
.withAddress("receiver@example.com")
.withType(Message.RecipientType.TO)
.build())
.withPlainText("Hello")
.withDeliveryStatusNotification(
DeliveryStatusNotification.ReturnOption.HEADERS_ONLY,
DeliveryStatusNotification.NotifyOption.FAILURE,
DeliveryStatusNotification.NotifyOption.DELAY)
.buildEmail();
Property-file defaults are available as:
simplejavamail.defaults.delivery.status.notification.notify=FAILURE,DELAY
simplejavamail.defaults.delivery.status.notification.return.option=HEADERS_ONLY
Per-body Content-Transfer-Encoding defaults
Impact: Existing global content-transfer encoding behavior remains the fallback. Use the new per-body settings when text, HTML and calendar parts need different encodings.
The existing withContentTransferEncoding(...) setting can now be refined per body part.
Email email = EmailBuilder.startingBlank()
.from("sender@example.com")
.withRecipients(new RecipientBuilder()
.withAddress("receiver@example.com")
.withType(Message.RecipientType.TO)
.build())
.withPlainText("Plain")
.withHTMLText("<b>HTML</b>")
.withContentTransferEncoding(ContentTransferEncoding.QUOTED_PRINTABLE)
.withPlainTextContentTransferEncoding(ContentTransferEncoding.BIT7)
.withHTMLTextContentTransferEncoding(ContentTransferEncoding.BASE_64)
.buildEmail();
Property-file defaults are available as:
simplejavamail.defaults.content.transfer.encoding=quoted-printable
simplejavamail.defaults.body.text.content.transfer.encoding=7bit
simplejavamail.defaults.body.html.content.transfer.encoding=base64
simplejavamail.defaults.body.calendar.content.transfer.encoding=quoted-printable
Pre-encoded attachments and embedded images
Impact: New API only. Existing attachment APIs still represent raw data that Simple Java Mail may encode for MIME transport.
When attachment or embedded-image data is already encoded, use the new pre-encoded APIs so Simple Java Mail preserves the payload instead of encoding it again.
Email email = EmailBuilder.startingBlank()
.from("sender@example.com")
.withRecipients(new RecipientBuilder()
.withAddress("receiver@example.com")
.withType(Message.RecipientType.TO)
.build())
.withPlainText("See attachment")
.withPreEncodedAttachment(
"report.pdf",
alreadyBase64EncodedBytes,
"application/pdf",
ContentTransferEncoding.BASE_64)
.buildEmail();
The same concept exists for embedded images using
withPreEncodedEmbeddedImage(...).
Explicit resource Content-ID values
Impact: New API only. Generated fallback Content-ID values are now opaque and safe, while caller-supplied values are preserved.
Attachment and embedded-image APIs now support separating the display filename/resource name from the MIME
Content-ID. This matters for round-tripping and for HTML that references a stable
cid:... value.
Email email = EmailBuilder.startingBlank()
.from("sender@example.com")
.withRecipients(new RecipientBuilder()
.withAddress("receiver@example.com")
.withType(Message.RecipientType.TO)
.build())
.withHTMLText("<img src='cid:logo-content-id'>")
.withEmbeddedImage("logo.png", logoDataSource, "logo-content-id")
.buildEmail();
Jakarta Mail debug output targets
Impact: New API only. Existing withDebugLogging(true) behavior remains available.
Jakarta Mail debug output can now be redirected to one of the built-in targets, or to a custom
PrintStream from Java code.
Mailer mailer = MailerBuilder
.withSMTPServer("smtp.example.com", 587, "user", "password")
.withDebugLogging(true)
.withDebugOutput(SessionDebugOutput.SLF4J)
.buildMailer();
Property-file configuration uses:
simplejavamail.javaxmail.debug=true
simplejavamail.javaxmail.debug.out=SLF4J
Local bind address configuration
Impact: New API only. This hides the transport-specific Jakarta Mail property names behind Simple Java Mail configuration.
On multi-IP machines, the outgoing SMTP socket can now be bound to a specific local/source address. The local port is supported as an advanced option, but should normally be left empty.
Mailer mailer = MailerBuilder
.withSMTPServer("smtp.example.com", 587, "user", "password")
.withTransportStrategy(TransportStrategy.SMTP_TLS)
.withLocalBindAddress("203.0.113.10")
.buildMailer();
Property-file configuration uses:
simplejavamail.smtp.localaddress=203.0.113.10
simplejavamail.smtp.localport=25252
See the feature docs and configuration reference for the full examples.
Mailer-level default DKIM signing
Impact: New API only. Existing per-email DKIM signing still takes precedence.
DKIM signing can now be configured once on the mailer. This is a convenience wrapper around the mailer defaults mechanism and preserves other configured email defaults.
Mailer mailer = MailerBuilder
.withSMTPServer("smtp.example.com", 587, "user", "password")
.withDefaultDkimSigning(defaultDkimConfig)
.buildMailer();
Property-driven DKIM defaults continue to use simplejavamail.dkim.signing.*.
See DKIM signing for usage.
Simple sequential batch and open-connection sending
Impact: New API only. This does not replace the batch-module.
Mailer.sendMailsInSimpleBatch(...) sends multiple emails sequentially over one SMTP connection. It is meant for
caller-managed loops where reconnecting for each email is the only problem to solve.
mailer.sendMailsInSimpleBatch(emails); // uses the mailer's configured async default
mailer.sendMailsInSimpleBatch(emails, false); // blocks until the sequential batch is done
mailer.sendMailsInSimpleBatch(emails, true); // schedules the whole sequential batch asynchronously
This API is not concurrent, pooled, retry-oriented or clustered. For those use cases, keep using the batch-module. See batch and clustering support for the full distinction.
For caller-owned queues that need custom work between successful sends, use Mailer.withOpenConnection(...). It opens one
SMTP connection for the callback and provides a scoped sender; each send is still sequential and processed like a normal blocking send.
mailer.withOpenConnection(sender -> {
while (database.hasPendingMail()) {
Email next = database.nextPendingMail();
sender.sendMail(next);
database.markSent(next);
}
});
Batch-module cluster configuration
Impact: Cluster pool defaults are now scoped per cluster key instead of effectively behaving like one global JVM-level pool default.
With the Java API, the first Mailer registered for a cluster key defines that cluster's pool defaults,
claim timeout, expiry and load-balancing strategy. Different cluster keys can now have different defaults.
Property-file and Spring users can also define multiple cluster configs using
simplejavamail.defaults.connectionpool.clusters.*.
Mailer ordersMailer = MailerBuilder
.withSMTPServer("orders-smtp.example.com", 587, "user", "password")
.withClusterKey(ordersCluster)
.withConnectionPoolMaxSize(3)
.withConnectionPoolLoadBalancingStrategy(LoadBalancingStrategy.RANDOM_ACCESS)
.buildMailer();
simplejavamail.defaults.connectionpool.clusters.orders.clusterkey.uuid=00000000-0000-0000-0000-000000000301
simplejavamail.defaults.connectionpool.clusters.orders.maxsize=3
simplejavamail.defaults.connectionpool.clusters.orders.loadbalancing.strategy=RANDOM_ACCESS
See clustering configuration for alias-based, UUID-keyed and Spring examples.
Async mailer defaults apply to testConnection
Impact: If a mailer is configured with async(), no-arg
testConnection() now follows that configured default.
This aligns testConnection() with no-arg sendMail(...): both use the mailer's
configured async default. Use testConnection(false) when a blocking connection test is required.
Resource filenames moved out of Content-Type filename
Impact: Only code or tests that inspect generated raw MIME headers should need attention.
Simple Java Mail no longer adds the non-standard filename parameter to attachment and embedded-image
Content-Type headers. The filename remains available from the standard
Content-Disposition filename parameter, while
Content-Type keeps the legacy-compatible name parameter.
If you compare generated EML literally, update expectations from this:
Content-Type: application/pdf; filename=report.pdf; name=report.pdf
Content-Disposition: attachment; filename=report.pdf
to this:
Content-Type: application/pdf; name=report.pdf
Content-Disposition: attachment; filename=report.pdf
Java module descriptors
Impact: New metadata only. Classpath users do not need to change anything.
The facade and core jars now include Java module descriptors, completing the multi-release jar support for modular applications. Applications using
the Java Platform Module System can require org.simplejavamail directly.
module com.example.mailer {
requires org.simplejavamail;
}
See modules for the module layout.