Expand my Community achievements bar.

Adobe Summit 2025: AEM Session Recordings Are Live! Missed a session or want to revisit your favorites? Watch the latest recordings now.
SOLVED

AEM 6.5 trailing slash on void elements

Avatar

Level 8

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>
1 Accepted Solution

Avatar

Correct answer by
Community Advisor

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).

View solution in original post

5 Replies

Avatar

Correct answer by
Community Advisor

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).

Avatar

Level 8

Thanks so much for your detailed answer.
@AmitVishwakarma can you please suggest how would you go about customizing the response (your suggested option 1)

 

Avatar

Community Advisor

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

Avatar

Level 8

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>");

Avatar

Level 8

Hi @AmitVishwakarma 

 

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)