Skip to main content

Spring Boot Integration

The mjml-java-spring module provides Spring Boot auto-configuration for the MJML renderer.

Installation

Add the Spring module to your project:

<dependency>
<groupId>dev.jcputney</groupId>
<artifactId>mjml-java-spring</artifactId>
<version>1.0.1</version>
</dependency>

This transitively includes mjml-java-core. The mjml-java-resolvers module is an optional dependency -- add it explicitly if you need advanced resolvers.

Auto-Configuration

When mjml-java-spring is on the classpath, the following beans are auto-configured:

BeanTypeDescription
mjmlIncludeResolverIncludeResolverA SpringResourceIncludeResolver that resolves includes from the configured template location
mjmlConfigurationMjmlConfigurationConfiguration built from spring.mjml.* properties
mjmlServiceMjmlServiceService for rendering MJML templates
thymeleafMjmlServiceThymeleafMjmlServiceThymeleaf + MJML composition service (requires Thymeleaf on classpath and spring.mjml.thymeleaf-enabled=true, which is the default)

All beans use @ConditionalOnMissingBean, so you can override any of them by defining your own bean of the same type.

Configuration Properties

Configure the renderer via application.properties or application.yml:

spring:
mjml:
language: en
direction: ltr
sanitize-output: true
max-input-size: 1048576
max-nesting-depth: 100
max-include-depth: 50
template-location: classpath:mjml/
include-allowed-schemes: classpath,file
thymeleaf-enabled: true
PropertyDefaultDescription
spring.mjml.language"und"HTML lang attribute
spring.mjml.direction"auto"Text direction: "ltr", "rtl", "auto"
spring.mjml.sanitize-outputtrueEscape HTML special characters in attribute values
spring.mjml.max-input-size1048576Maximum input size in characters
spring.mjml.max-nesting-depth100Maximum element nesting depth
spring.mjml.max-include-depth50Maximum nested include depth
spring.mjml.template-location"classpath:mjml/"Base location for template resolution
spring.mjml.include-allowed-schemesclasspath,fileAllowed schemes for include resource paths
spring.mjml.thymeleaf-enabledtrueEnable Thymeleaf integration (auto-detected)

MjmlService

MjmlService is the primary bean for rendering MJML in a Spring application:

import dev.jcputney.mjml.spring.MjmlService;
import dev.jcputney.mjml.MjmlRenderResult;

@Service
public class EmailService {

private final MjmlService mjmlService;

public EmailService(MjmlService mjmlService) {
this.mjmlService = mjmlService;
}

public String renderWelcomeEmail(String userName) {
String mjml = """
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>Welcome, %s!</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
""".formatted(userName);

return mjmlService.render(mjml);
}
}

MjmlService methods:

MethodReturnsDescription
render(String mjml)StringRenders MJML to HTML (returns just the HTML string)
renderResult(String mjml)MjmlRenderResultRenders MJML and returns the full result (HTML, title, preview text)
getConfiguration()MjmlConfigurationReturns the configuration used by this service

SpringResourceIncludeResolver

The auto-configured include resolver uses Spring's ResourceLoader to resolve include paths. By default, only safe local schemes are allowed:

  • classpath: -- classpath resources
  • file: -- file system paths

Relative paths are resolved against the configured template-location.

To allow remote schemes, configure them explicitly:

spring:
mjml:
include-allowed-schemes: classpath,file,https
# Templates in src/main/resources/mjml/
spring:
mjml:
template-location: classpath:mjml/
<!-- In your MJML template -->
<mj-include path="partials/header.mjml" />
<!-- Resolves to classpath:mjml/partials/header.mjml -->

Thymeleaf Integration

When Thymeleaf is on the classpath and spring.mjml.thymeleaf-enabled is true (the default), a ThymeleafMjmlService bean is auto-configured. This service processes Thymeleaf expressions first, then renders the resulting MJML to HTML.

import dev.jcputney.mjml.spring.ThymeleafMjmlService;

@Service
public class EmailService {

private final ThymeleafMjmlService thymeleafMjmlService;

public EmailService(ThymeleafMjmlService thymeleafMjmlService) {
this.thymeleafMjmlService = thymeleafMjmlService;
}

public String renderWelcomeEmail(String userName) {
String mjmlTemplate = """
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>Welcome, [[${name}]]!</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
""";

return thymeleafMjmlService.render(mjmlTemplate,
Map.of("name", userName));
}
}

Using Template Files

ThymeleafMjmlService can also load templates by name from the Thymeleaf template resolver:

// Loads the template from the Thymeleaf template path (e.g., classpath:templates/welcome)
String html = thymeleafMjmlService.renderTemplate("welcome",
Map.of("name", "Alice", "activationUrl", "https://example.com/activate"));

ThymeleafMjmlService methods:

MethodDescription
render(String mjmlTemplate, Map<String, Object> variables)Processes an inline MJML string through Thymeleaf, then renders to HTML
renderTemplate(String templateName, Map<String, Object> variables)Loads a template by name via Thymeleaf, processes it, then renders as MJML to HTML

Overriding Auto-Configuration

Override any auto-configured bean by defining your own:

@Configuration
public class MjmlConfig {

@Bean
public IncludeResolver mjmlIncludeResolver() {
// Use a file system resolver instead of the default classpath resolver
return new FileSystemIncludeResolver(Path.of("/opt/templates"));
}

@Bean
public MjmlConfiguration mjmlConfiguration(IncludeResolver resolver) {
return MjmlConfiguration.builder()
.language("en")
.direction("ltr")
.includeResolver(resolver)
.contentSanitizer(html -> Jsoup.clean(html, Safelist.basic()))
.build();
}
}