OWASP Java HTML Sanitizer: Prevent XSS in Spring Boot with Practical Examples

This article demonstrates how to use the OWASP Java HTML Sanitizer library to prevent DOM-based XSS attacks in Spring Boot applications, covering dependency setup, pre-packaged and custom sanitization policies, advanced preprocessors, and change listeners with complete code examples.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
OWASP Java HTML Sanitizer: Prevent XSS in Spring Boot with Practical Examples

Introduction to DOM-Based XSS and the Need for Sanitization

In real-world development, directly inserting unprocessed user input into innerHTML, document.write, or rendering data from URL parameters, localStorage, or API responses as HTML easily triggers DOM-based XSS. These vulnerabilities bypass server-side defenses; Content Security Policy (CSP) cannot block them, and backend filtering is ineffective. Even if script tags are filtered, attackers can use SVG event handlers or javascript: pseudo-protocols to circumvent protections. Therefore, strict sanitization must be applied at every dynamic insertion point.

OWASP Java HTML Sanitizer Overview

The OWASP Java HTML Sanitizer is an open-source library from Google. It is a fast, easily configurable Java HTML cleaner that allows web applications to include third-party HTML while preventing cross-site scripting (XSS) attacks. The sanitizer JAR has zero runtime dependencies; its only compile-time dependency is spotbugs-annotations (provided scope, used only for annotations). Other JARs are required only for the test suite. The code follows security best practices, includes an extensive test suite, and has passed adversarial security reviews.

Dependency Setup

Environment: Spring Boot 3.5.0. Add the following Maven dependency (requires JDK 11 or higher):

<dependency>
  <groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
  <artifactId>owasp-java-html-sanitizer</artifactId>
  <version>20260924.2</version>
</dependency>

Source code is available at https://github.com/OWASP/java-html-sanitizer. To build all Maven modules and run tests, execute ./mvnw clean verify from the repository root. The Maven wrapper locks the required Maven version. When building a specific module with -pl, also use -am to automatically build its dependencies.

Repository Structure

owasp-java-html-sanitizer/ – contains the released library files, corresponding test cases, and the Java module descriptor.

java8-shim/ and java10-shim/ – provide compatibility adaptation code packaged into the final library JAR.

examples/ – offers tested, reusable example filtering policies (not formally released).

empiricism/ – holds browser experiment code and data used to generate the sanitizer's element rule tables.

docs/ – includes user documentation, security-related documents, and historical archives.

Quick Start: Pre-packaged Policies

The library provides ready-made policies such as Sanitizers.FORMATTING and Sanitizers.LINKS. They can be combined using .and().

String untrustedHTML = """
    <script>alert("xxxooo")</script>
    <h1>Spring Boot3实战案例300讲<script>document.wirte("xxxooo")</script></h1>
    """;
PolicyFactory policy = Sanitizers.FORMATTING.and(Sanitizers.LINKS);
String safeHTML = policy.sanitize(untrustedHTML);
System.err.println(safeHTML);

Output: Spring Boot3实战案例300讲 The malicious script tags are stripped, leaving only safe formatting and link elements.

Custom Policies with HtmlPolicyBuilder

Allow Specific Elements and Attributes

Define a policy that permits only a elements with https URLs and enforces rel="nofollow":

String untrustedHTML = """
    <script>alert("xxxooo")</script>
    <h1>Spring Boot3实战案例300讲<script>document.wirte("xxxooo")</script></h1>
    <a href="javascript:alert('hello')">你好</a>
    """;
PolicyFactory policy = new HtmlPolicyBuilder()
    .allowElements("a")
    .allowUrlProtocols("https")
    .allowAttributes("href").onElements("a")
    .requireRelNofollowOnLinks()
    .toFactory();
String safeHTML = policy.sanitize(untrustedHTML);
System.err.println(safeHTML);

Output:

Spring Boot3实战案例300讲
你好

The script tags and the javascript: protocol link are removed; only the plain text of the link remains.

Element Transformation

Convert heading tags ( h1 – h6) into div elements with a custom class:

String untrustedHTML = """
    <script>alert("xxxooo")</script>
    <h1>Spring Boot3实战案例300讲<script>document.wirte("xxxooo")</script></h1>
    <a href="javascript:alert('hello')">你好</a>
    """;
PolicyFactory policy = new HtmlPolicyBuilder()
    .allowElements("p")
    .allowElements(
        (String elementName, List<String> attrs) -> {
            // Add class attribute
            attrs.add("class");
            attrs.add("header-" + elementName);
            // Return the element name to include; empty string deletes it.
            return "div";
        }, "h1", "h2", "h3", "h4", "h5", "h6")
    .toFactory();
String safeHTML = policy.sanitize(untrustedHTML);
System.err.println(safeHTML);

Output:

<div class="header-h1">Spring Boot3实战案例300讲</div>
你好

The h1 becomes a div with class header-h1; scripts and the unsafe link are dropped.

Attribute Policies with Custom Logic

Allow data-foo attributes on div and span with a custom matcher:

PolicyFactory myPolicy = new HtmlPolicyBuilder()
    .allowElements("div", "span")
    .allowAttributes("data-foo")
        .matching(
            (String elementName, String attributeName, String value) -> {
                // Return the attribute value, or null to delete.
                return value;
            })
        .onElements("div", "span")
    .toFactory();

If an element like a, font, img, input, or span should be allowed even when it has no attributes, explicitly whitelist it with allowWithoutAttributes(). Adding attribute policies does not weaken default checks (e.g., style or URL attribute validation).

Advanced Features

Preprocessors

Preprocessors enable insertion of text and large-scale structural changes before policy application. They do not affect output safety because they run prior to policy enforcement.

Example: Append the origin (scheme + authority) of each link's URL in parentheses after the link text.

String untrustedHTML = """
    <script>alert("xxxooo")</script>
    <h1>Spring Boot3实战案例300讲<script>document.wirte("xxxooo")</script></h1>
    <a href="javascript:alert('hello')">你好</a>
    """;
PolicyFactory myPolicy = new HtmlPolicyBuilder()
    .withPreprocessor(
        (HtmlStreamEventReceiver r) -> {
            return new HtmlStreamEventReceiverWrapper(r) {
                @Override
                public void openTag(String elementName, List<String> attrs) {
                    if ("a".equals(elementName)) {
                        for (int i = 0, n = attrs.size(); i < n; i += 2) {
                            if ("href".equals(attrs.get(i))) {
                                String url = attrs.get(i + 1);
                                String origin;
                                try {
                                    URI uri = new URI(url);
                                    String scheme = uri.getScheme();
                                    String authority = uri.getRawAuthority();
                                    if (scheme == null && authority == null) {
                                        origin = null;
                                    } else {
                                        origin = (scheme != null ? scheme + ":" : "")
                                            + (authority != null ? "//" + authority : "");
                                    }
                                } catch (URISyntaxException ex) {
                                    origin = "about:invalid";
                                }
                                if (origin != null) {
                                    text(" (" + origin + ") ");
                                }
                            }
                        }
                    }
                    super.openTag(elementName, attrs);
                }
            };
        })
    .allowElements("a")
    .allowAttributes("href").onElements("a")
    .allowStandardUrlProtocols()
    .toFactory();
String safeHTML = myPolicy.sanitize(untrustedHTML);
System.err.println(safeHTML);

Output:

Spring Boot3实战案例300讲
  (javascript:) 你好

The preprocessor adds (javascript:) after the link text before the policy sanitizes the javascript: protocol (which is later removed by allowStandardUrlProtocols()).

Change Listeners

When a policy rejects an element or attribute, it notifies an HtmlChangeListener. This can be used to track policy violations and detect attempts to bypass security controls.

String untrustedHTML = """
    <script>alert("xxxooo")</script>
    <h1>Spring Boot3实战案例300讲<script>document.wirte("xxxooo")</script></h1>
    <a href="javascript:alert('hello')">你好</a>
    """;
PolicyFactory policy = ...;
String safeHTML = policy.sanitize(untrustedHTML, new HtmlChangeListener<Object>() {
    @Override
    public void discardedTag(Object context, String elementName) {
        System.err.println(elementName);
    }
    @Override
    public void discardedAttributes(Object context, String elementName, String... attributeNames) {
        System.err.println("%s, %s".formatted(elementName, Arrays.toString(attributeNames)));
    }
}, Object.class);
System.err.println(safeHTML);

Output:

script
script
a

<div class="header-h1">Spring Boot3实战案例300讲</div>
你好

The listener logs each discarded tag ( script, script, a) and the final sanitized HTML. When an element is dropped because all its attributes were rejected (e.g., a link whose sole href was removed), discardedAttributes is also triggered. Two additional default methods provide more detail: discardedAttribute receives the value of each rejected attribute, and discardedText receives tag-like content removed from retained literal elements or content the renderer could not output.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Spring BootJava securityHTML SanitizationXSS PreventionDOM XSSOWASP Java HTML SanitizerPolicy ConfigurationSecurity Library
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.