Sending Email with Spring MVC Using Template
This tutorial walks through a small Spring MVC application that sends a templated e-mail, using Thymeleaf to render the message body and Jakarta Mail to deliver it.
It assumes you are comfortable with Java EE development and with building Spring MVC applications.
Note: This post has been updated for Spring Framework 7 and Thymeleaf 3.1. The original version rendered the message with Apache Velocity, but Spring deprecated its Velocity support in 4.3 and removed it entirely in 5.0; templating is now handled by Thymeleaf, and the mail dependency has moved from
javax.mailto Jakarta Mail (jakarta.mail).
Spring Framework’s Support for E-mail
Built on top of Jakarta Mail, the Spring Framework provides a high-level abstraction that hides most of the boilerplate of sending e-mail. The key types in the diagram above are worth a quick look:
MailSenderis the root interface, with a singlesend()contract for plain-text messages carried bySimpleMailMessage.JavaMailSenderextends it to support MIME messages (HTML bodies, attachments, inline images).JavaMailSenderImplis the implementation you configure as a bean; it wraps a Jakarta MailSession.MimeMessageHelperandMimeMessagePreparatormake populating aMimeMessage(recipients, subject, HTML body) far less verbose than the raw Jakarta Mail API.
We will configure JavaMailSenderImpl as a bean and let Thymeleaf produce the HTML body.
Dependencies (pom.xml)
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>7.0.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>7.0.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>7.0.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>7.0.8</version>
</dependency>
<!-- Provides the org.springframework.mail package -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>7.0.8</version>
</dependency>
<!-- Jakarta Mail (API + Eclipse Angus implementation) -->
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-mail</artifactId>
<version>2.0.4</version>
</dependency>
<!-- Thymeleaf (template engine + Spring 7 integration) -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring7</artifactId>
<version>3.1.5.RELEASE</version>
</dependency>
The first group is the usual Spring MVC stack; spring-context-support is the module that actually provides the org.springframework.mail package. jakarta.mail-api together with the Eclipse Angus implementation replaces the old com.sun.mail/javax.mail pair, and the two Thymeleaf artifacts replace Velocity (thymeleaf-spring7 is the integration module for Spring Framework 7).
Configuring the Mail Sender and Template Engine (dispatcher-servlet.xml)
<context:annotation-config />
<context:component-scan base-package="org.verwandlung.voj.web" />
<!-- SMTP connection -->
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="smtp.mailgun.org" />
<property name="username" value="postmaster@verwandlung.org" />
<property name="password" value="" />
<property name="javaMailProperties">
<props>
<prop key="mail.smtp.auth">true</prop>
<prop key="mail.smtp.starttls.enable">true</prop>
</props>
</property>
</bean>
<!-- Thymeleaf engine dedicated to e-mail templates -->
<bean id="emailTemplateResolver" class="org.thymeleaf.templateresolver.ClassLoaderTemplateResolver">
<property name="prefix" value="mails/" />
<property name="suffix" value=".html" />
<property name="templateMode" value="HTML" />
<property name="characterEncoding" value="UTF-8" />
<property name="cacheable" value="false" />
</bean>
<bean id="emailTemplateEngine" class="org.thymeleaf.spring7.SpringTemplateEngine">
<property name="templateResolver" ref="emailTemplateResolver" />
</bean>
JavaMailSenderImpl holds the SMTP connection details (here, Mailgun’s relay). The ClassLoaderTemplateResolver tells Thymeleaf to load templates from mails/ on the classpath with an .html suffix, and SpringTemplateEngine is the engine our code calls to render them. Because the MailSender helper below is annotated with @Component, component scanning picks it up automatically, so it needs no explicit bean of its own.
The Mail Template (verifyEmail.html)
File location: classpath:/mails/verifyEmail.html
<div style="padding:0;margin:0;background:#e9e9e9;font-family:'Segoe UI', 'Microsoft YaHei'">
<table width="625" style="background:#e9e9e9" cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr>
<td>
<div style="color:#1b1b1b;background:#fff;border:1px solid #aaa;border-radius:5px;margin:15px;">
<table cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr>
<td style="padding:15px 15px 0 15px;background:#fff;border-radius:4px 4px 0 0;">
<div>
<img src="https://verwandlung.org/assets/img/logo.webp" alt="Testzilla" height="60" width="250">
</div>
</td>
</tr>
<tr>
<td style="padding:15px; background:#fff; border-radius:0 0 4px 4px;font-size:12px;">
Hi <span th:text="${username}">User</span>, <br />
There's just one step left to create your Verwandlung Online Judge account. <br /><br />
To access your account and be eligible to receive testing projects, we need you to confirm your email address. <br />
<a th:href="@{https://www.verwandlung.org/account/verifyEmail(email=${email},code=${code})}" href="#">Click here to confirm your email address</a>. <br /><br />
Thank you, <br />
The Verwandlung Online Judge Team. <br /><br />
<div style="border-top:3px solid #eee;color:#999;font-size:11px;line-height:1.2">
<br>Powered by <a href="https://verwandlung.org" target="_blank" style="color: #005399;text-decoration: none;">Testzilla</a>. All rights reserved.<br>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
</div>
Thymeleaf replaces Velocity’s ${...} placeholders with th:* attributes: th:text sets element content (escaping it safely), and th:href with the @{...(param=value)} syntax builds the confirmation URL and URL-encodes each query parameter for you.
The Mail Sender Class
package org.verwandlung.voj.web.util;
import java.util.Map;
import jakarta.mail.internet.MimeMessage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.stereotype.Component;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring7.SpringTemplateEngine;
@Component
public class MailSender {
private final SpringTemplateEngine templateEngine;
private final JavaMailSender mailSender;
private final Logger logger = LogManager.getLogger(MailSender.class);
@Autowired
public MailSender(SpringTemplateEngine templateEngine, JavaMailSender mailSender) {
this.templateEngine = templateEngine;
this.mailSender = mailSender;
}
public String getMailContent(String templateName, Map<String, Object> model) {
Context context = new Context();
context.setVariables(model);
return templateEngine.process(templateName, context);
}
public void sendMail(String recipient, String subject, String body) {
MimeMessagePreparator preparator = mimeMessage -> {
MimeMessageHelper message = new MimeMessageHelper(mimeMessage, "UTF-8");
message.setTo(recipient);
message.setFrom("noreply@verwandlung.org");
message.setSubject(subject);
message.setText(body, true);
};
mailSender.send(preparator);
logger.info("An email {Recipient: {}, Subject: {}} has been sent.", recipient, subject);
}
}
getMailContent() copies the model into a Thymeleaf Context and renders the named template to a String. sendMail() then hands that HTML to a MimeMessagePreparator (a lambda here) and asks JavaMailSender to deliver it; passing true to setText() marks the body as HTML.
How to Use It
package org.verwandlung.voj.web.service;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.verwandlung.voj.web.util.MailSender;
@Service
@Transactional
public class UserService {
@Autowired
private MailSender mailSender;
public void sendActivationMail(String username, String email, String code) {
Map<String, Object> model = new HashMap<>();
model.put("username", username);
model.put("email", email);
model.put("code", code);
String subject = "Activate Your Account";
String body = mailSender.getMailContent("verifyEmail", model);
mailSender.sendMail(email, subject, body);
}
}
getMailContent("verifyEmail", model) resolves to mails/verifyEmail.html through the prefix and suffix configured on the resolver, so the service only needs the bare template name.
Conclusion
With Velocity gone, Thymeleaf is the natural template engine for e-mail on modern Spring: the resolver and engine beans take the place of Velocity’s VelocityEngineFactoryBean, while the JavaMail-based sending code is unchanged apart from the move to the jakarta.mail package. The same MailSender helper can render any template under mails/, so adding a password-reset or welcome e-mail is just another .html file.

The Disqus comment system is loading ...
If the message does not appear, please check your Disqus configuration.