Skip to content
Happy Programming Guide
Start learning
Java

Java Security

The security work that matters in Java applications: SQL injection, deserialization, dependency vulnerabilities, password hashing, XML parsing and secrets handling.

A small, turquoise-colored combination padlock hangs from a rusty metal latch on a weathered door

Most Java vulnerabilities come from a small set of recurring mistakes, and each has a well-understood fix. This guide covers the ones that matter in ordinary application code: injection, unsafe deserialization, vulnerable dependencies, password storage, XML parsing, randomness and secrets handling.

1. SQL injection#

JAVA
// Vulnerable
String sql = "SELECT * FROM users WHERE email = '" + email + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);

An input of ' OR '1'='1 returns every row. Prepared statements send the query and the data separately, so input cannot change the query’s structure:

JAVA
String sql = "SELECT id, name FROM users WHERE email = ?";

try (PreparedStatement stmt = connection.prepareStatement(sql)) {
    stmt.setString(1, email);
    try (ResultSet rs = stmt.executeQuery()) {
        while (rs.next()) {
            System.out.println(rs.getString("name"));
        }
    }
}

With JPA, the same rule applies — use named parameters, never string building.

2. Deserialization#

JAVA
// Dangerous with untrusted input
ObjectInputStream in = new ObjectInputStream(request.getInputStream());
Object obj = in.readObject();

Java deserialization can construct arbitrary objects and invoke their methods during construction. With the right classes on the classpath, that becomes remote code execution — the mechanism behind a long series of high-profile vulnerabilities.

The rule is simple: never deserialize data you did not create. Use JSON with a well-configured parser instead:

JAVA
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// Do not enable default typing - that reintroduces the same class of problem

Order order = mapper.readValue(json, Order.class);

Where Java serialization is unavoidable, apply a filter:

JAVA
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
    "com.example.model.*;java.base/*;!*");
in.setObjectInputFilter(filter);

3. Vulnerable dependencies#

In practice this is where most real risk sits. Your code may be fine; a library three levels down may not be.

Terminal
mvn org.owasp:dependency-check-maven:check
gradle dependencyCheckAnalyze

Run it in your build pipeline so a new advisory fails the build rather than waiting to be noticed. Dependabot or Renovate will raise the update pull requests automatically.

4. Password storage#

JAVA
// Never
String hash = DigestUtils.md5Hex(password);
String hash = DigestUtils.sha256Hex(password);

MD5 and SHA-256 are designed to be fast, which is exactly wrong for passwords — it makes brute-force cheap. Use an algorithm designed to be slow:

JAVA
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

PasswordEncoder encoder = new BCryptPasswordEncoder(12);

String hash = encoder.encode(rawPassword);      // includes its own salt
boolean ok = encoder.matches(rawPassword, hash);

Argon2 is the current recommendation where you can use it; bcrypt remains perfectly acceptable. Store the result in a column of at least 100 characters — a truncated hash fails verification for every user, and the cause is not obvious from the symptom.

5. XML external entities#

JAVA
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature(
    "http://xml.org/sax/features/external-general-entities", false);
factory.setFeature(
    "http://xml.org/sax/features/external-parameter-entities", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);

Without these, a crafted XML document can read files from your server or make it issue network requests. The defaults in older Java versions are unsafe, so set them explicitly on every parser you create.

6. Path traversal#

JAVA
// Vulnerable
File file = new File("/var/data/" + request.getParameter("name"));

A name of ../../etc/passwd escapes the folder. Resolve and verify:

JAVA
Path base = Paths.get("/var/data").toRealPath();
Path target = base.resolve(name).normalize();

if (!target.startsWith(base)) {
    throw new SecurityException("Path outside the permitted directory");
}

normalize() collapses the dot segments; the startsWith check is what actually enforces the boundary.

7. Command injection#

JAVA
// Vulnerable - a shell interprets the whole string
Runtime.getRuntime().exec("convert " + userInput + " out.png");

// Safer - arguments are passed separately, no shell involved
new ProcessBuilder("convert", userInput, "out.png").start();

Better still, avoid running external commands on user input at all. Where you must, validate the input against a strict allow-list first.

8. Randomness#

JAVA
// Predictable - fine for shuffling, unsafe for tokens
Random random = new Random();

// Cryptographically secure
SecureRandom secure = new SecureRandom();
byte[] token = new byte[32];
secure.nextBytes(token);
String value = Base64.getUrlEncoder().withoutPadding().encodeToString(token);

Anything that grants access — session identifiers, password reset tokens, API keys — must come from SecureRandom.

9. Secrets#

JAVA
// Never in source
private static final String API_KEY = "sk-1234567890";

// From the environment
String apiKey = System.getenv("API_KEY");
if (apiKey == null || apiKey.isBlank()) {
    throw new IllegalStateException("API_KEY is not set");
}

Failing loudly at start-up is better than making a request with an empty key and getting a confusing 401 later. Once a secret is in git history, rotating it is the only real fix — removing it from the current file is not enough.

10. TLS#

Never install a trust manager that accepts every certificate. Disabling validation to make a development environment work removes the entire protection TLS provides, and that code invariably reaches production. Add the certificate to a trust store instead.

A checklist#

  • Every query uses parameters, including in admin code
  • No Java deserialization of untrusted input
  • A dependency scan runs in the build
  • Passwords use bcrypt or Argon2, column at least 100 characters
  • XML parsers have external entities disabled
  • File paths are resolved and checked against a base directory
  • Tokens come from SecureRandom
  • Secrets come from the environment or a secret manager
  • The JDK and all dependencies are on supported versions

Questions people ask#

Is Java more secure than other languages?

It removes whole classes of memory-safety bugs that C and C++ have. It does not remove injection, deserialization or configuration mistakes, which is where application vulnerabilities actually come from.

Do I need the SecurityManager?

No — it is deprecated for removal. Use operating system and container isolation instead, which is where that responsibility now sits.

Should I use Spring Security?

For anything with authentication, yes. It provides password encoding, CSRF protection, session handling and security headers correctly by default. Writing that yourself means getting every detail right.

How often should I update dependencies?

Patch releases as they appear, ideally automatically. Minor and major versions on a regular schedule. A codebase that is years behind is far harder to secure than one kept current.

Where to go next#

Java web development, where these rules applyRead next

Keep reading

Java

Java Performance Optimization

Where Java applications actually spend their time, how to profile before you guess, and the fixes that matter: collections, strings, streams and…

5 min read

Java

Java Not Working in VS Code

No IntelliSense, "cannot find symbol" on everything, or the run button does nothing? VS Code needs a JDK it can find and…

4 min read

Java

What’s New in Java 25 LTS

Java 25 is the current long-term release. Flexible constructors, compact source files, module imports and scoped values are final; here is what…

4 min read

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *