Preventing cross-site scripting (XSS) in Java applications
Learn how to prevent cross-site scripting (XSS) in Java applications by applying input validation, context-aware output encoding, and proven security libraries. This guide covers common XSS attack vectors, best practices, and tools to help developers secure their web applications and APIs effectively.
Your Information will be kept private.
Begin your DAST-first AppSec journey today.
Request a demo
Cross-site scripting (XSS) remains one of the most common and dangerous web application security vulnerabilities. While modern frameworks and browsers offer some protections, developers building Java web applications still need to take proactive steps to prevent XSS attacks.
What is XSS and why Java apps are at risk
XSS allows attackers to inject malicious scripts—typically JavaScript—into web pages viewed by other users. These scripts can hijack sessions, steal sensitive data, deface pages, or redirect users to malicious sites. Java applications are particularly exposed because they often handle dynamic content, user-generated data, and complex templating, all of which can open the door to XSS vulnerabilities if not properly handled.
Even though Java runs on the server side, its web layer (like JSP, servlets, or frameworks such as Spring Boot) frequently renders HTML that’s consumed on the client side. If user input is inserted into this HTML without proper encoding or sanitization, attackers can easily exploit it.
Types of XSS attacks: Understanding reflected, stored, and DOM-based XSS
As with any other web application, Java applications can be vulnerable to several types of XSS:
- Reflected XSS: Malicious input is sent in an HTTP request (often via a
RequestParam
) and reflected immediately in the web page or response. - Stored XSS: Malicious payloads are stored in a backend (like a database) and later rendered on a web page, potentially exposing all future visitors to attack.
- DOM-based XSS: JavaScript code is executed entirely in the Document Object Model of the page in the user’s browser, making DOM-based attacks undetectable on the server side.
How Java web applications can be exploited
Common exploit scenarios include injecting <script>
tags, breaking out of HTML attributes, or manipulating JavaScript code execution on the client side. Attackers often tamper with input fields, URL parameters, JSON or XML payloads, and even HTTP headers to smuggle in malicious code. If a Java application fails to validate or encode this input, it risks exposing sensitive information and enabling cross-site scripting vulnerabilities.
Common entry points for XSS in Java
User input in JSP/Servlet pages
JSP and servlet-based applications are frequent targets because they often mix raw user input directly into the web page. Fields like comments, search queries, and form submissions can become attack vectors if not handled securely.
Dynamic content rendering
Many Java applications dynamically render HTML, JavaScript, or CSS based on user data. Without proper output encoding, attackers can slip malicious code into this dynamic content.
Improper HTML encoding
Failure to correctly encode special characters like <
, >
, "
, and '
can allow attackers to break out of intended HTML or JavaScript contexts. For example, inserting "><script>alert(1)</script>
into a search box might trigger an XSS vulnerability if the output isn’t properly escaped.
Best practices to prevent XSS in Java
Validate and sanitize user input
Start with strict input validation using allowlists, especially on RequestParam
, form fields, or API payloads. For example, if expecting only alphanumeric characters, explicitly reject or strip special characters. Where rich text inputs are needed and explicitly permitted, use dedicated sanitization libraries like OWASP Java HTML Sanitizer or JSoup to remove or neutralize dangerous HTML tags and attributes. Note that validation alone does not guarantee XSS protection and should always be combined with output encoding.
Use context-aware output encoding
Proper encoding is by far the most effective defense against XSS. It ensures that special characters in user data are safely displayed without being interpreted as code. Examples of encoding include:
- HTML encoding: Replace
<
with<
,>
with>
, and&
with&
to prevent breaking HTML structure. - Attribute encoding: Escape quotes and special characters when inserting data into HTML attributes.
- JavaScript encoding: When embedding user data inside JavaScript, encode it to avoid breaking out of the script context.
Use libraries like the OWASP Java Encoder to handle HTML, JavaScript, CSS, and URL encoding safely, automatically, and consistently.
Escape data in JSPs with JSTL
JavaServer Pages Standard Tag Library (JSTL) tags such as <c:out>
automatically HTML-escape user data, making them preferable to raw expressions like ${}
. This reduces the risk of accidentally introducing XSS through unescaped output.
Use proven security libraries
Rather than relying on manual escapes or regex, use proven security libraries such as the ones already mentioned: OWASP Java Encoder, OWASP Java HTML Sanitizer, or JSoup. In most cases, a dedicated library will be more reliable and efficient than any custom solution while also easing code maintenance and updates.
Use the right security headers
Regardless of the language, setting the right Content Security Policy (CSP) header can block entire classes of attacks, including many varieties of XSS, and should be done as a matter of course. Note that the dedicated X-XSS-Protection
header is now obsolete in modern browsers and should not be relied upon as a defense against cross-site scripting.
Framework-based XSS protections for Java
Depending on your specific Java application framework, built-in security measures may be available for XSS and other common vulnerabilities. As with encoding libraries, it’s usually better to use the built-in features than try to roll your own.
Headers and XSS filters in Spring Security
The Spring Security framework is primarily intended for authentication and authorization, but it also offers a variety of features labeled as “protection against exploits.” These include built-in support for common security headers, some default-on security header settings, and request filtering. While dedicated anti-CSRF features are also provided, the only XSS-specific security feature is the now-deprecated X-XSS-Protection
header. Built-in support makes it easy to set CSP headers, but CSP is not enabled by default because content policies are typically site-specific.
Jakarta EE and built-in controls
Jakarta EE (formerly Java EE) doesn’t explicitly offer any XSS protection, but it does provide security-related mechanisms like servlet filters and tag libraries to help you enforce secure output encoding and input validation. Again, as you follow XSS prevention best practices, it’s best to take advantage of built-in capabilities wherever possible.
Testing and monitoring for XSS in Java applications
Cross-site scripting vulnerabilities can hide in unexpected places, especially in complex Java applications that span multiple layers, from raw servlets to JSPs to modern frameworks like Spring Boot. Even when developers follow secure coding practices, it’s not always clear whether those protections are consistently effective. That’s why continuous testing and runtime monitoring are essential for uncovering XSS vulnerabilities and ensuring long-term application security, with dynamic testing leading the way to verify exploitability.
Static analysis to guide secure coding
Static application security testing (SAST) tools analyze Java source code, configuration files, and templates to identify potential security flaws such as improper output encoding or unsanitized input handling. While SAST is useful for enforcing secure coding standards during development, it lacks runtime context. It can’t see how different components behave together or whether user input actually reaches a vulnerable sink. This often leads to noisy results, including false positives and low-risk issues that don’t need immediate action.
In the case of XSS, SAST may flag any direct use of user input in an HTML context, even if proper encoding is applied elsewhere. This is why SAST should be treated as a guide to secure coding and not as a way to verify exploitability.
Runtime defense with RASP
Runtime application self-protection (RASP) tools monitor application behavior at runtime and can detect or block suspicious activity, including some types of XSS payloads. In Java applications, RASP can provide an additional layer of protection by intercepting dangerous input before it causes harm. However, RASP is not a substitute for secure development and thorough testing. It is reactive by nature and depends on known attack patterns, which may not cover more subtle or obfuscated XSS attempts. Because it runs on the server, it is also limited to detecting reflected and stored payloads and cannot detect purely client-side DOM-based XSS.
Used properly, RASP can help contain the impact of XSS attacks that slip through other defenses, but it should complement—not replace—secure coding practices, SAST, and DAST.
Finding exploitable XSS vulnerabilities with DAST
Dynamic application security testing (DAST) simulates real-world attacks against a running web application, making it the most effective way to determine whether XSS vulnerabilities are truly exploitable. Unlike static tools that analyze source code or configuration files, DAST requires no code access and operates from the outside in—just like an attacker. It sends crafted payloads through actual HTTP requests and observes how the application responds. This makes DAST particularly valuable in Java environments, where multiple layers of filtering, validation, and encoding may interact in unexpected ways.
For example, a Java web application might implement input validation in a Spring controller, output encoding in a JSP, and additional filtering in a servlet filter. Each of these layers could appear secure in isolation, but only DAST can confirm whether they work together effectively to block real XSS attempts. This holistic, black-box perspective is critical because a security vulnerability isn’t about what code is present but what an attacker can actually exploit.
Advanced DAST solutions like Invicti also include proof-based scanning to eliminate false positives for many types of XSS vulnerabilities, so developers don’t waste time chasing down theoretical issues that can’t be triggered in practice (or don’t exist in the first place). In a DAST-first approach, teams can focus their resources on first fixing what matters most: vulnerabilities that attackers could actually exploit in the deployed application.
Secure coding can reduce XSS risk—but only DAST can verify it
Preventing cross-site scripting in Java applications requires a layered defense: input validation, output encoding, secure frameworks, and the right use of headers. But no matter how thorough the development process is, there’s only one way to know if all these defenses actually work together in production: testing the application from the outside in. And that means dynamic testing.
DAST doesn’t assume; it verifies. It doesn’t analyze code in isolation but checks whether an attacker can break through. In a Java stack where behavior can vary across JSPs, servlets, and controller logic, DAST provides the only realistic way to validate that XSS vulnerabilities have been properly eliminated. Whether you’re using Spring Boot, Jakarta EE, or legacy JSP pages, adopting a DAST-first mindset and application security workflow is the most reliable way to prevent XSS vulnerabilities from reaching users.
Frequently asked questions (FAQ)
Can Java alone prevent XSS?
No. Java provides some helpful APIs and libraries, but secure development practices, input validation, output encoding, and proper configuration are essential for effective XSS prevention.
Does using JSP increase XSS risk?
Yes, particularly if developers output dynamic user data without escaping it. Use JSTL tags and context-aware encoding to reduce this risk.
What encoding library is best for Java?
The OWASP Java Encoder is widely recommended due to its comprehensive coverage of HTML, JavaScript, and other encoding contexts, as well as its active maintenance and community support.