Most Java security flaws fall into a handful of predictable categories — insecure deserialization, injection, XML external entity processing, and unpatched third-party dependencies — and nearly all of them are fixable with configuration changes and library upgrades you can ship this week. The language itself is not the problem. The JVM is memory-safe, so you don't fight buffer overflows the way C developers do. What bites Java teams instead is the ecosystem: decades-old serialization APIs, XML parsers that trust their input by default, and a dependency tree that quietly pulls in libraries with known CVEs. This guide walks through the flaws that show up in real incident reports and gives you the concrete fix for each.
Why insecure deserialization is Java's signature flaw
If you learn one Java security flaw, make it deserialization. ObjectInputStream.readObject() will reconstruct any serializable class on the classpath from a byte stream, and if an attacker controls that stream, they can chain together "gadget" classes whose side effects during deserialization lead to remote code execution. This is not theoretical — the Apache Commons Collections gadget chain has been weaponized against WebLogic, JBoss, Jenkins, and countless custom apps since 2015.
The root cause is that Java deserialization runs code before your application ever sees a validated object. A payload can invoke InvokerTransformer chains that ultimately call Runtime.exec().
The fix is to stop deserializing untrusted data with native Java serialization entirely. Use JSON with an explicit schema instead. Where you genuinely cannot avoid ObjectInputStream, install a serialization filter (available since Java 9):
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.myapp.dto.*;java.base/*;!*");
ObjectInputStream ois = new ObjectInputStream(input);
ois.setObjectInputFilter(filter);
The !* at the end rejects everything not explicitly allowlisted. Treat any code path that deserializes bytes from an HTTP request, a message queue, or a cache as hostile.
Injection is not just SQL
Injection remains the most common exploitable Java security flaw because it appears in more places than developers expect. SQL injection is the famous one, and the fix is boring and total: use PreparedStatement with bound parameters, never string concatenation.
// Vulnerable
String q = "SELECT * FROM users WHERE email = '" + email + "'";
// Safe
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM users WHERE email = ?");
ps.setString(1, email);
But injection extends well past SQL. Command injection happens when user input reaches Runtime.exec() or a ProcessBuilder. LDAP injection hits directory lookups built by concatenation. Expression Language injection has produced RCE in Spring and JSF applications where user input is evaluated as SpEL. Log injection lets attackers forge log entries or, as Log4Shell demonstrated in 2021, trigger JNDI lookups. The common thread: any time you build an interpreted string from untrusted input, you have a potential injection. Parameterize, escape for the specific interpreter, and validate against an allowlist.
XML external entities: the parser that trusts too much
Java's default XML parsers — DocumentBuilderFactory, SAXParserFactory, XMLInputFactory — resolve external entities out of the box. That means a crafted XML document can read local files (file:///etc/passwd), perform server-side request forgery against internal services, or trigger a billion-laughs denial of service. This is the XXE flaw, and it ships enabled by default in most JDK versions.
Disabling it takes a few lines that every Java team should templatize:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
Disabling DOCTYPE declarations outright is the strongest single control. If your application genuinely needs DTDs, disable external entity resolution specifically and cap entity expansion.
Weak cryptography and secrets in code
A recurring Java security flaw is reaching for the wrong crypto primitive or leaving a key in the source tree. MessageDigest.getInstance("MD5") and SHA-1 still appear in password hashing code; both are unfit for that purpose. Use a purpose-built password hash — BCrypt, SCrypt, or Argon2 through a library like Spring Security's PasswordEncoder. For symmetric encryption, Cipher.getInstance("AES") silently defaults to ECB mode, which leaks plaintext structure; always specify a mode and padding, such as AES/GCM/NoPadding, and never reuse a nonce.
Hardcoded secrets are the other half of this problem. API keys and database passwords committed to Git have leaked more credentials than any exploit. Move them to environment variables or a secrets manager, and scan your history — a secret that ever touched a public repo should be considered burned and rotated.
The dependency flaws you didn't write
Here's the uncomfortable truth: most of the exploitable Java security flaws in your application are in code you never wrote. A typical Spring Boot service pulls in 100-plus transitive dependencies, and any one of them can carry a known CVE. Log4Shell (CVE-2021-44228) was a two-line configuration flaw in a logging library that ran in nearly every Java shop on earth. The Jackson databind library has had a long series of deserialization CVEs tied to polymorphic type handling — we cover the specifics in the Jackson databind security guide.
You cannot manually track this. Software composition analysis exists precisely to map your dependency tree against known-vulnerability databases and flag transitive risk you'd otherwise never see. A tool such as Safeguard can surface a vulnerable version of commons-collections that arrived four layers deep through a library you added for something unrelated. Pair automated scanning — see how it fits into a build in our SCA overview — with a policy of keeping dependencies current, because most CVEs are patched in a release you simply haven't adopted yet.
Access control and session flaws
Broken access control tops the OWASP Top 10 for a reason, and Java web apps get it wrong in consistent ways. Insecure direct object references — passing a database ID in a URL and trusting it — let one user read another's records when the server skips the ownership check. Fix this by verifying, on every request, that the authenticated principal actually owns the resource, not just that they're logged in.
Session management flaws follow close behind. Session IDs that don't rotate after login enable session fixation; cookies without the HttpOnly and Secure flags are readable by scripts and travel over plaintext. In Spring Security, set sessionFixation().changeSessionId() and configure cookies as secure and HTTP-only. Disable CSRF protection only when you have a stateless token scheme that makes it redundant, never as a shortcut to make a form submit.
FAQ
What is the most dangerous Java security flaw?
Insecure deserialization of untrusted data is the most dangerous because it can lead directly to remote code execution without any memory-corruption trickery. Any endpoint that calls readObject() on attacker-controllable bytes should be treated as a critical risk and either removed or protected with a strict serialization filter.
Does the JVM's memory safety protect me from most attacks?
It protects you from an entire class of C-style memory-corruption bugs, but not from the injection, deserialization, XXE, access-control, and dependency flaws that make up nearly all real-world Java exploits. Memory safety is a floor, not a ceiling.
How do I find Java security flaws in code I didn't write?
Use software composition analysis to inventory every direct and transitive dependency and match it against vulnerability databases like the NVD and GitHub Advisory Database. Manual tracking does not scale past a handful of libraries, and transitive dependencies are where most unpatched CVEs hide.
Is npm audit-style scanning available for Java?
Yes. Tools like OWASP Dependency-Check, and commercial SCA platforms, do for Maven and Gradle what dependency scanners do for npm — flag known-vulnerable versions in your build. Integrate one into CI so a vulnerable dependency fails the pipeline before it reaches production.