In my AEM 6.5 template, I have the following code snippet. When it gets rendered on the publish instance, the <br> tag is automatically converted to a self-closing <br/>tag, even though no self-closing tag is present in the original template. The same behavior occurs with the <meta...> tag, which is rendered as <meta..../> the same as other void elements. Could you help me understand why this happens? I’m asking because the W3C Validator flags this as an issue, stating: "Trailing slash on void elements has no effect and interacts badly with unquoted attribute values."
Here’s an example of the rendered output:
<head> <meta charset="UTF-8"/> </head>
Here is my AEM template snippet:
<li>
<sly data-sly-call="${iconTpl.icon @ icon='ui-checkmark', additionalClasses='c-icon--no-inline t-highlight t-highlight--positive'}"></sly>
<a target="_blank" rel="noreferrer" href="https://something" >Bonusprogram.</a>
tralalala<br>
</li>
Solved! Go to Solution.
Views
Replies
Total Likes
Hi @anasustic ,
This behavior you're seeing in AEM 6.5—where void elements like <br> and <meta> are rendered with a trailing slash (<br/>, <meta/>)—is due to the XML-style serialization done by HTL (Sightly) or the underlying Sling engine, which defaults to XHTML output mode in many scenarios.
Why this happens
HTL (HTML Template Language) in AEM doesn't render templates as plain HTML. Instead, it uses XML serialization rules from the underlying parsers (like Xerces or SAX). According to XHTML standards, void elements like <br>, <meta>, <img> are self-closing, hence it outputs them as <br/>, <meta/>, etc.
This is compliant with XHTML, but not strictly HTML5, which is what the W3C validator is flagging.
W3C Validator Warning
The W3C HTML validator warns:
“Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.”
That’s because in HTML5, void elements must not be self-closed (e.g., <br> is correct, but <br/> is discouraged though still technically allowed in most browsers).
${'<br>' @ context='unsafe'}
Use this sparingly, as it bypasses XSS protection.
Regards,
Amit
Views
Replies
Total Likes
Hi @anasustic ,
This behavior you're seeing in AEM 6.5—where void elements like <br> and <meta> are rendered with a trailing slash (<br/>, <meta/>)—is due to the XML-style serialization done by HTL (Sightly) or the underlying Sling engine, which defaults to XHTML output mode in many scenarios.
Why this happens
HTL (HTML Template Language) in AEM doesn't render templates as plain HTML. Instead, it uses XML serialization rules from the underlying parsers (like Xerces or SAX). According to XHTML standards, void elements like <br>, <meta>, <img> are self-closing, hence it outputs them as <br/>, <meta/>, etc.
This is compliant with XHTML, but not strictly HTML5, which is what the W3C validator is flagging.
W3C Validator Warning
The W3C HTML validator warns:
“Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.”
That’s because in HTML5, void elements must not be self-closed (e.g., <br> is correct, but <br/> is discouraged though still technically allowed in most browsers).
${'<br>' @ context='unsafe'}
Use this sparingly, as it bypasses XSS protection.
Regards,
Amit
Views
Replies
Total Likes
Thanks so much for your detailed answer.
@AmitVishwakarma can you please suggest how would you go about customizing the response (your suggested option 1)
Views
Replies
Total Likes
Hi @anasustic ,
Using a Servlet Filter that intercepts the response and modifies the serialized output before it’s sent to the client.
Servlet Filter to Post-process Output
1. Create the Servlet Filter
Create a filter that wraps the response and modifies the output stream to replace XHTML-style void elements (<br/>, <meta/>) with their HTML5 equivalents (<br>, <meta>).
package com.example.core.filters;
import org.apache.commons.io.output.TeeOutputStream;
import org.osgi.service.component.annotations.Component;
import org.osgi.framework.Constants;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
@Component(service = Filter.class,
property = {
Constants.SERVICE_RANKING + ":Integer=100",
"sling.filter.scope=REQUEST"
})
public class HtmlVoidElementFixFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
CharResponseWrapper responseWrapper = new CharResponseWrapper((HttpServletResponse) response);
chain.doFilter(request, responseWrapper);
String originalContent = responseWrapper.toString();
// Replace self-closing void elements with HTML5-compliant ones
String updatedContent = originalContent
.replaceAll("<br\\s*/>", "<br>")
.replaceAll("<meta([^>]*)\\s*/>", "<meta$1>");
response.getWriter().write(updatedContent);
}
}
2. Create the CharResponseWrapper
This wrapper captures the output of the servlet before it's sent to the browser.
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
public class CharResponseWrapper extends HttpServletResponseWrapper {
private final CharArrayWriter output;
public CharResponseWrapper(HttpServletResponse response) {
super(response);
output = new CharArrayWriter();
}
@Override
public PrintWriter getWriter() {
return new PrintWriter(output);
}
@Override
public String toString() {
return output.toString();
}
}
What This Does
- Intercepts the HTML output
- Rewrites self-closed tags like <br/> or <meta .../> into <br> and <meta ...>
- Keeps the rest of the response untouched
Note:
- This works only for text/html responses (which most AEM pages are).
- Be careful not to overmatch or accidentally rewrite self-closing tags inside <script> or <style>.
Regards,
Amit
Great thank you very much @AmitVishwakarma
I implemented and tested your code for self closing void elements:
// Replace self-closing void elements with HTML5-compliant ones
String updatedContent = originalContent
.replaceAll("<br\\s*/>", "<br>")
.replaceAll("<meta([^>]*)\\s*/>", "<meta$1>")
.replaceAll("<link([^>]*)\\s*/>", "<link$1>")
.replaceAll("<img([^>]*)\\s*/>", "<img$1>")
.replaceAll("<input([^>]*)\\s*/>", "<input$1>")
.replaceAll("<col([^>]*)\\s*/>", "<col$1>")
.replaceAll("<source([^>]*)\\s*/>", "<source$1>")
.replaceAll("<hr([^>]*)\\s*/>", "<hr$1>");
Just for my understanding, I am wondering if your code is actually implementing your second suggested option (2. Post-process HTML output)?
If that is the case, I was wondering if you could suggest how would you go about implementing your first suggested option => 1. Switch serialization mode to HTML5 (not XHTML)
Views
Replies
Total Likes