Add fluent MimeMessage builder as a modern alternative to MimeMessageHelper
Author: dominik-kovacsCreated Jul 7, 2026Updated Sep 22, 2026
Labelsstatus: waiting-for-triagein: core
What I'd like
A fluent way to build MIME messages without the ceremony that MimeMessageHelper requires:
var message = MimeMessageBuilder.builder()
.from("[email protected]", "App Name")
.to(List.of("[email protected]"))
.cc(ccList)
.subject("Hello")
.text("Plain text fallback")
.html("<h1>Hello</h1>")
.attachment("report.pdf", bytes, "application/pdf")
.header("X-Custom", "value")
.build();
javaMailSender.send(message);What we do today
Every project I've seen (including ours) ends up with something like this:
javaMailSender.send(mimeMessage -> {
var multipart = hasText(req.getHtml()) || !req.getAttachments().isEmpty();
var helper = new MimeMessageHelper(mimeMessage, multipart, StandardCharsets.UTF_8.name());
helper.setTo(req.getTo().toArray(String[]::new));
if (req.getCc() != null) {
helper.setCc(req.getCc().toArray(String[]::new));
}
if (req.getBcc() != null) {
helper.setBcc(req.getBcc().toArray(String[]::new));
}
if (hasText(req.getHtml()) && hasText(req.getText())) {
helper.setText(req.getText(), req.getHtml());
} else if (hasText(req.getHtml())) {
helper.setText(req.getHtml(), true);
} else {
helper.setText(req.getText());
}
if (hasText(req.getFromName())) {
helper.setFrom(req.getFrom(), req.getFromName());
} else {
helper.setFrom(req.getFrom());
}
if (hasText(req.getSubject())) helper.setSubject(req.getSubject());
if (hasText(req.getReplyTo())) helper.setReplyTo(req.getReplyTo());
// attachments...
});It's not terrible, but it's a lot of defensive code for something that should be straightforward. The main annoyances:
- You have to figure out the
multipartflag yourself before you even start setCc(null)throws, so every optional field needs a null check- Lists need
toArray(String[]::new)every time - Text vs HTML vs both requires a three-way branch
- None of this is really "our" logic — it's just wiring
Suggested behavior
- Multipart mode figured out automatically from whether there's HTML/attachments
- Null or empty optional fields just get ignored (no exception)
- Takes collections directly
- If both
.text()and.html()are called, it does the right thing (multipart/alternative) - UTF-8 by default
I think of it as the same kind of improvement RestClient brought over RestTemplate — the imperative API is still there, but the common case gets much nicer.
Happy to take a stab at a PR if there's interest.
Source: spring-projects/spring-framework