If you tried to write the history of Java security as a list of CVEs, it would quickly swell into a ledger you could never finish reading. The sheer number of bugs keeps growing, and the identifiers get longer every year — yet reading through them, it is hard to see the two things that matter more: how the center of gravity of the research shifted, step by step, and why attackers keep finding new ways into the Java ecosystem.
Black Hat's talks over the years are a good thread for tracing that arc. They are not a complete vulnerability database, nor can they represent the full body of research in any given year, but they do reflect changes at several levels quite clearly: what researchers cared about most at the time, which attack techniques had matured into practice, and which problems were moving from small-scale academic discussion toward large-scale real-world attacks.
This article spans 2002–2026. The "twenty years" in the title is a round figure for "a bit over two decades"; the actual window is closer to 24 years.
1. 2002—2010: the JVM was the first gate to be watched
Black Hat Asia 2002 had a very on-the-nose title: Java and Java Virtual Machine Security Vulnerabilities and Their Exploitation Techniques. It covered Java's built-in security mechanisms, the Applet sandbox, the SecurityManager, the bytecode verifier, and the security bugs and sandbox-escape techniques found in the various JVM implementations of the day.[1]
That same year, Black Hat USA featured Security Aspects in Java Bytecode Engineering: A Tutorial, which started from the JVM architecture and bytecode, and discussed how bytecode tooling could be used to analyze and modify programs.[2]
All of this fits how Java was typically used at the time. The browser would download an Applet from the network and hand it to the local JVM to run. Remote code could execute, but in theory it was confined to the sandbox — unable to read files, open network connections, or call local resources at will.
The most important lines of defense back then were:
- the bytecode verifier, checking that class files and instructions were legal;
- the ClassLoader, separating trusted local code from remote code;
- the SecurityManager, restricting sensitive operations according to policy;
- the Applet sandbox, keeping remote code inside a constrained environment.
Early Java security research therefore revolved largely around type safety, class loading and permission checks. If you could make the JVM misjudge the type of a piece of bytecode, or coax a high-privilege class into acting on behalf of untrusted code, the sandbox could be bypassed.
JavaSnoop brought the problem back to client-side trust
By 2010, Black Hat USA's JavaSnoop: How to Hack Anything Written in Java offered a different angle: attach directly to a running JVM and modify class bytecode, method arguments and return values — even intercepting and altering an object before it goes onto the network.[3]
The same conference's Hacking Java Clients also focused on the Java client itself, exploring client-side business logic, authorization decisions, and what happens once a runtime object is tampered with.[3:1]
This body of work put a plain but often-overlooked fact on the table:
As long as code runs on a machine the user controls, a permission check inside the client cannot count as a real security boundary.
License checks, role decisions, amount calculations and feature flags can all live on the client to improve the experience, but the server must re-verify. Obfuscation, signing and private fields raise the cost of analysis at best; they do not change the premise that the client is always in the user's hands.
2. 2011—2013: Java browser plugins hit their exploitation peak
People often call Java a "memory-safe language," but that claim only partly holds at the language level. The JVM, JIT, JNI, and font- and graphics-processing components still contain a great deal of native code.
Black Hat Abu Dhabi 2011's Exploiting Memory Corruption Vulnerabilities in the Java Runtime dealt specifically with exploiting memory-corruption bugs in the JRE, and the problems you must solve to build a reliable exploit.[4]
In 2012, Recent Java Exploitation Trends and Malware shifted the lens to real-world attacks. The official abstract noted that more and more Java vulnerabilities were being used to spread malware, and that Java's cross-platform nature let a single sandbox breach potentially cover Windows, macOS and Unix. The talk used CVE-2012-0507 as an example of such a bug already being used for large-scale infection.[5]
Java was attractive to attackers in this period for reasons that are not complicated:
- huge desktop install base;
- browser plugins that directly received content from the network;
- many endpoints that had gone a long time without a JRE upgrade;
- sandbox escapes that were often easier to make reliable than a full native exploit chain;
- one Java exploit that could cover multiple operating systems.
2013's Java Every-Days: Exploiting Software Running on 3 Billion Devices took another step forward. It explicitly noted that the research focus was no longer confined to traditional memory corruption, but was starting to abuse Java's dynamic capabilities — such as the reflection API — to achieve remote code execution.[6]
In other words, an attacker did not necessarily have to corrupt heap memory. Simply bypassing access restrictions to reach classes, methods or objects that should have been off-limits was enough to call high-privilege APIs.
This chapter of history basically closed by 2026
Early Java sandboxing relied on the SecurityManager and Applets. Later, browser plugins left the mainstream, and the platform itself began cleaning up these legacy mechanisms.
JEP 411 marked the SecurityManager for removal in JDK 17; JEP 486 permanently disabled it in JDK 24; and JEP 504 removed the Applet API in JDK 26.[7][8][9]
This does not mean Java no longer needs isolation — it means the location of that isolation has shifted. Today the more reliable boundary usually lies outside the JVM: separate processes, containers, OS users, file permissions, network policy and cloud identity.
3. 2014—2019: the main battlefield moved to the server side
After browser plugins exited, Java security did not cool off. Enterprise Java web applications, RPC, messaging systems, application servers and middleware took over as the primary attack surface.
Runtime dataflow began to be traced automatically
Black Hat USA 2014's RAVAGE — full name Runtime Analysis of Vulnerabilities and Generation of Exploits — traced complete dataflow through normal program execution to find bugs, generate exploits, and feed the results into existing test frameworks. The researchers also released a Java version.[10]
This kind of work suits Java well. The class-file format is uniform, type and method information is relatively complete, and the JVM provides instrumentation. Research tools can observe method calls, object propagation and sensitive operations without needing the full source.
From here on, analyzing a Java vulnerability was no longer just a matter of searching for a dangerous function — it meant answering three questions:
- where does external input enter;
- which objects, callbacks and type conversions does it pass through;
- does it actually reach a file, the network, process execution or dynamic class loading in the end.
2016: Java Serialization became an enterprise-grade entry point
Black Hat USA 2016's Pwning Your Java Messaging with Deserialization Vulnerabilities put Java deserialization into messaging systems. The talk pointed out that message handling across the Java ecosystem leans heavily on Java Serialization, so a message consumer may expose an unsafe-deserialization entry point too.[11]
The problem with native Java deserialization is far more than "it parsed an untrusted file."ObjectInputStream.readObject() restores an entire object graph, and along the way it may fire methods like readObject、readResolve、compareand getters. The attacker does not need to upload new code to the server — as long as the target classpath contains the right classes, these seemingly ordinary methods can be strung into a gadget chain.
A typical chain has four parts:
- attacker-controlled serialized data appears over the network, in a file, in a message, or via RPC;
- the server calls Java Object Serialization to restore the object;
- the object graph fires the methods of a set of existing classes in sequence;
- execution finally lands in a sink such as file writing, network access, script execution, template execution or process creation.
There is often only one entry point, but what really decides exploitability is the whole classpath. The more dependencies an application has, the more candidate gadgets there are, and the harder the judgment becomes.
The same year, JNDI was formally established as an attack primitive
Another key talk at Black Hat USA 2016 was A Journey from JNDI/LDAP Manipulation to Remote Code Execution Dream Land. The researchers introduced "JNDI Reference Injection," showing that once an attacker-controlled name reaches a JNDI lookup, it can — via services like RMI, LDAP and CORBA — turn around and affect the server running the query.[11:1]
JNDI was originally an infrastructure API for enterprise Java, used to find DataSources, EJBs, message queues and directory objects. The problem is precisely that applications let untrusted input decide the lookup name, the provider URL, or even the directory content.
The real danger point is usually not the single method InitialContext.lookup() , but several conditions holding at once:
- external input can control the lookup name or target;
- the JNDI provider supports remote objects or references;
- the server can actively reach an attacker-controlled network location;
- the query result triggers object creation, class loading or other dynamic behavior.
This line of research would surface again in Log4Shell.
Switching to JSON does not make object-injection go away
After Java deserialization was exposed at scale, many teams' first reaction was to switch to JSON. Black Hat USA 2017's Friday the 13th: JSON Attacks took direct aim at that assumption. The researchers analyzed mainstream JSON parsers in Java and .NET, showed that some libraries could still be turned into RCE under default or common configurations, and extended the approach to other serialization formats.[12]
The key is never whether the data looks like binary, XML or JSON — it is whether the parser decides the concrete type based on the input.
Once external data can control the polymorphic type, the class name, the construction process or property setters, so-called "data parsing" can quietly become object instantiation. As long as a dangerous class and a gadget can be chained on afterward, changing the format does not truly cut the attack path.
2018: gadget chains went automated, and cross-component interpretation gaps came into view
Black Hat USA 2018's Automated Discovery of Deserialization Gadget Chains proposed a method for automatically discovering Java gadget chains. Before this, plenty of tools could locate an unsafe deserialization entry point, but deciding whether it was actually exploitable still meant a human digging through the classpath. This talk put the emphasis on automatically finding usable call chains.[13]
The same year, Breaking Parser Logic: Take Your Path Normalization off and Pop 0days Out! focused on the path-normalization class of attack surface, covering many web frameworks including Java's.[13:1]
Placing these two together is illuminating: one is about the call relationships inside an object graph, the other about how proxies, containers and frameworks interpret the same path differently. Neither is a single-point dangerous-function problem — both are about components not following the same semantics.
By Black Hat Europe 2019, New Exploit Technique In Java Deserialization Attack was still on the schedule. If nothing else, the continuity of these talks shows that Java deserialization did not flare up in 2016 and then fizzle out.[14]
4. 2021: Log4Shell detonated years of accumulated problems at once
CVE-2021-44228, better known as Log4Shell. The NVD description notes that in the affected Log4j 2 versions, the JNDI feature did not properly guard against attacker-controlled LDAP and other JNDI endpoints, and that the entry point could come from configuration, a log message, or even an ordinary parameter.[15]
CISA later noted that Log4j had been integrated into thousands of products worldwide.[16] That also explains why, at the time, the hardest job for many companies was not writing WAF rules but first answering a more basic question: which systems in production actually shipped with this version of Log4j.
Log4Shell was not some brand-new trick out of nowhere. What it really did was pull together several long-standing threads of research into one:
- ordinary business input enters the log;
- the logging component treats the string as interpretable content;
- a Lookup mechanism triggers JNDI;
- the server is allowed to reach external targets such as LDAP;
- the component runs inside a privileged Java process;
- Log4j is transitively bundled and repackaged by a huge number of products.
The 2016 JNDI research made the dangerous capability clear; the 2021 Log4Shell then proved, in reality, just how large the blast radius gets once a low-level general-purpose library wires ordinary input to that capability.
It also exposed three management problems that have long existed in the Java ecosystem:
- dependencies may hide inside Fat JARs, WARs, EARs, plugin directories or vendor products;
- the dependency version in the build file is not necessarily the version actually loaded at runtime;
- even if the application code does not directly use a feature, a transitive dependency may still drag it in.
5. 2023—2025: tools no longer just find the entry — they prove the whole chain runs
ODDFuzz connected static analysis with directed greybox fuzzing
Black Hat USA 2023's ODDFuzz: Hunting Java Deserialization Gadget Chains via Structure-Aware Directed Greybox Fuzzing continued to tackle automated gadget-chain discovery.[17]
ODDFuzz first does lightweight static taint analysis to find candidate gadget chains, then uses structure-aware seed generation and directed greybox fuzzing to produce serialized objects that can trigger those candidates. Its goal is clear: let static analysis avoid missing things, and let dynamic validation hold down false positives.[18]
The experimental results in the paper: on the ysoserial dataset it found 16 of 34 known chains, where two comparison approaches found only 3; and on real targets such as WebLogic, Dubbo, Nexus and protostuff, it further found 6 previously unreported exploitable chains and obtained 5 CVEs.[18:1]
The most important advance here is not the numbers themselves, but the raising of the bar.
Finding readObject() is no longer enough; confirming that source reaches sink is no longer enough either. Tools began trying to construct real objects that satisfy field relationships, type constraints and runtime branches, and finally to prove that the chain can indeed be triggered.
JDD kept working on path explosion and payload construction
Black Hat Asia 2025's JDD talk — full name In-depth Mining of Java Deserialization Gadget Chains via Bottom-up Gadget Search and Dataflow-aided Payload Construction.[19]
The JDD project first summarizes call relationships into gadget fragments, then uses bottom-up search to connect candidate fragments, while using dataflow relationships to aid payload construction. Its paper was published at the 2024 IEEE Symposium on Security and Privacy, and the tool supports multiple deserialization protocols including JDK, Hessian and JSON.[20]
From 2018 through 2023 to 2025, the tooling trajectory is quite clear:
- in 2018, the focus was automatically finding call chains;
- in 2023, the focus was validating candidate chains with structure-aware fuzzing;
- in 2025, the focus was curbing path explosion and solving object field relationships and payload construction.
Java deserialization research has moved from "does a dangerous API exist" into "can we automatically produce a verifiable attack chain."
6. 2026: the new directions are semantic differences, middleware seams and the JVM runtime
The 2026 Black Hat Java talks no longer circled around deserialization alone; three directions stand out as especially worth watching.
Cast Attack: the same input, read as two different characters at the char and byte layers
Black Hat Asia 2026's Cast Attack: A New Threat Posed by Ghost Bits in Java studied data loss in Java type conversion, and how that loss gives rise to a new attack surface.[21]
Oracle's Java 26 API documentation puts it plainly for DataOutputStream.writeBytes(String) : for each character in the string, the high 8 bits are simply discarded when it is written out.[22]
The risk here is not that "type conversion throws an error" — quite the opposite: it tends to happen silently, throwing nothing. Suppose a security appliance, validation logic or audit log reads the original Unicode String, while a back-end component ends up using the low 8 bits after the high bits are dropped: the two layers then disagree about the same input.
This class of problem falls under parser differential or representation differential: the security check relies on one representation, while actual execution relies on another. Patching a single payload's signature is treating the symptom; what the developer really needs to audit is the full chain from encoding to type conversion.
For Java code, several patterns are worth auditing specifically:
chartobytenarrowing conversion;- using
writeBytes(String)for protocol, path or command data; - string-to-byte conversion with no explicit
Charset; - validation that happens before encoding, while the dangerous operation happens after;
- WAF, Servlet container and business framework using different decode rules.
Enterprise Java: pre-auth RCE is still hiding in the middleware
Black Hat USA 2026's Pre-auth RCE in Enterprise Java: When Middleware Becomes the Exploit pulled the focus back to enterprise Java platforms.[23]
The research team's public write-up says they audited 4 enterprise-Java platforms and found 12 vulnerabilities, including 1 sandbox escape and 4 pre-auth issues, and demonstrated two complete, independent pre-auth RCE chains in Bonita BPM and Apache OFBiz.[24]
None of these bugs relied on a single "universal gadget." The common pattern is:
- an external request first reaches a public entry point;
- the router, security filter, servlet dispatcher or SSO handler judges the request inconsistently;
- a surface meant only for internal components gets touched by an unauthenticated request;
- a deserializer, template engine or other execution sink turns reachability into code execution.
This kind of bug is the easiest to miss, because each component, viewed alone, looks perfectly reasonable. The router assumes the filter will block it; the filter assumes the dispatcher won't parse it this way; and the internal API assumes the caller has already passed authentication. The real vulnerability appears only after these misaligned assumptions are stitched together.
JVM memory shell: once you have RCE, runtime state becomes an object of study too
Black Hat Asia 2026 also had More JVM Memory Shells: JVM Memory Shell Auto Searching Program. The official schedule files it under Malware and Threat Hunting; the researcher describes an automated framework that combines static analysis, runtime instrumentation and in-memory inspection to discover JVM memory shells.[21:1][25]
The tricky thing about a JVM memory shell is that it does not necessarily correspond to a new file on disk. An attacker may rewrite an already-loaded class, register a new Filter, Listener or Servlet, hook a framework handler, or use a Java Agent and instrumentation to quietly change runtime behavior.
So file scanning and dependency scanning alone are far from enough. A modern Java defense also has to know:
- which classes are actually loaded in the JVM;
- which classes have been redefined at runtime;
- whether the Filter, Listener, Servlet and handler lists in the web container have changed;
- whether unusual ClassLoaders, Java Agents, threads or dynamic bytecode have appeared;
- whether runtime state matches the deployment baseline.
This direction shows that the horizon of Java security research has extended from "how to obtain code execution" to "how to detect residence and tampering inside the JVM."
7. Six trends you can read out of these talks
1. The security boundary expanded from inside the JVM to the whole system
In 2002, the core question was whether the bytecode verifier and sandbox could keep out untrusted code. By 2026, the core question has become: as a request passes through the WAF, router, Servlet container, framework, serializer and internal services, which layer got it wrong.
To audit a Java application today, you need at least the following on a single diagram:
- the JDK and JVM parameters;
- frameworks and middleware;
- the classpath and plugins;
- reverse proxies and API gateways;
- message queues and RPC;
- network egress;
- containers, cloud identity and runtime state.
Looking only at the business Controller usually gives you an incomplete picture.
2. Memory safety is not the end of Java security
The Java language removed a lot of use-after-free, buffer-overflow and manual-memory-management problems, but serious vulnerabilities can still come from reflection, deserialization, JNDI, expression languages, template engines, ClassLoaders, type conversion and path resolution.
What these problems exploit is precisely legitimate semantics. The attacker does not need to crash the JVM — only to coax the system into invoking existing features in a dangerous but "compliant" order.
3. The most dangerous place is usually the seam between components
Path normalization, Cast Attack and 2026's middleware pre-auth RCE all point to the same commonality: different components do not agree on the same input.
Common seams include:
- the raw URL versus the normalized URL;
- the Unicode string versus the final bytes;
- the proxy path versus the Servlet path;
- the public API versus the internal API;
- the type information in JSON versus the class Java actually loads;
- the build-time dependency versus the runtime classpath.
Each component passing its own tests does not mean the combination is still safe.
4. Vulnerability hunting moved from "find a suspect" to "prove it's exploitable"
RAVAGE, Gadget Inspector, ODDFuzz and JDD are all solving the same real problem: a static tool can easily hand you a source-to-sink path, but proving it can fire in a real classpath, with real object state and a real framework lifecycle, is far harder.
The high-value tools ahead will care more about:
- object and field constraints;
- reflection, proxies and callbacks;
- framework lifecycle;
- configuration and dependency versions;
- dynamic validation;
- minimal reproducible input;
- confirming runtime side effects.
5. Java supply-chain problems ultimately land at runtime
After Log4Shell, SBOM and Software Composition Analysis became hot topics — and they matter — but Java easily falls into the awkward situation where "what you see at build time is one set, and what gets loaded at runtime is another."
Fat JARs, WARs, application-server shared directories, plugins, Java Agents, container images and vendor packaging all change the actual classpath. A genuinely useful asset inventory has to answer what the running JVM has loaded, not just what pom.xml says.
6. Old problems and new problems will coexist for a long time
That 2026 is still researching pre-auth RCE and JVM memory shells in Enterprise Java is exactly what shows that legacy middleware, old protocols and Java Serialization are far from gone in production; meanwhile, Cast Attack pulls attention back to the difference between characters and bytes.
The hallmark of the Java ecosystem is never that one era completely replaces the last, but that old and new attack surfaces pile up together. A modern Spring application may still depend on a serialization protocol designed more than a decade ago, run on an even older application server, and be wrapped in a newer API gateway on top.
8. What to focus on when doing Java security today
1. Reduce deserialization entry points first, then talk about filters
Across trust boundaries, avoid Java Object Serialization wherever possible. HTTP, message queues, caches, RPC and file imports should all use data models with a clear structure, and restrict which types may be instantiated.
When you cannot remove it immediately, you can use JEP 290's ObjectInputFilter to limit class, array size, object-graph depth, reference count and stream size; JEP 415 further added context-specific and dynamically selected filters.[26][27]
But filters are a compensating measure, not a get-out-of-jail-free card. You also have to check whether an exploitable gadget exists in the current classpath, and confirm the framework isn't creating its own ObjectInputStream that bypasses the global configuration.
2. JNDI names and provider URLs must not be decided by external input
Search for and track, as a priority:
InitialContext.lookup(name);
Context.lookup(name);
JndiTemplate.lookup(name);
Don't just check whether a parameter "looks like a fixed prefix." Keep checking whether string concatenation, environment variables, headers, message properties, database configuration and directory content can influence the final lookup.
On the network side, Java services should by default be restricted from reaching external LDAP, RMI, CORBA and unnecessary DNS targets. Many dynamic-loading chains require the server to reach out; egress control can cut that link directly.
3. Validation and use must be based on the same normalized result
Paths, URLs, headers, Unicode and protocol fields should undergo one explicit conversion at the entry point. Authentication, routing, logging and business logic should use the same canonical representation as much as possible.
When converting strings to bytes, specify the Charsetexplicitly, avoid using writeBytes(String) on security-sensitive data, and don't perform a lossy narrowing conversion after validation.
Test with real deployment combinations — CDN, WAF, reverse proxy, Servlet container and framework. Only calling the Controller in unit tests will never surface a parser differential.
4. Internal APIs need their own authentication and authorization
Don't treat "not reachable from external routes" as the only protection for an internal interface. Internal APIs, management endpoints, serializers, template engines and script executors should each do authentication, authorization and input constraints at their own entry.
If a component truly should only be called internally, prefer a dedicated listener, network namespace, mTLS or workload identity — not just a path prefix.
5. The dependency inventory must cover the runtime classpath
Beyond SCA and SBOM, you should periodically collect:
- the JARs and modules actually loaded by the JVM;
- dependencies embedded in Fat JARs, WARs and EARs;
- shared libraries on the application server;
- plugins and Java Agents;
- duplicate versions inside container base images;
- the actual load source of same-named classes;
- dynamically downloaded and cached artifacts.
When responding to a vulnerability, being able to quickly answer "which JVMs actually loaded the affected class" is more useful than just searching Git repositories.
6. Establish a baseline for the JVM runtime
In production you can record a baseline of class loading, ClassLoaders, Java Agents, Filters, Listeners, Servlets, handlers, threads and key system properties.
Trigger an investigation when you see changes like:
- an unknown Java Agent attaching;
- class retransformation or redefinition;
- newly added Filters, Listeners or Servlets;
- unusual ClassLoaders;
- classes that exist only in memory and map to no deployment artifact;
- suspicious threads and scheduled tasks;
- a framework registry changing at runtime.
7. Squeeze post-RCE privileges to the minimum
A Java process should not, by default, have the rights to write the application directory, execute system commands, reach cloud metadata, read all secrets, or connect to every internal service.
More practical lines of defense include:
- a dedicated low-privilege OS user;
- read-only filesystem;
- a minimized container image;
- a network egress allowlist;
- a least-privilege ServiceAccount and cloud identity;
- isolating the sensitive management plane from the business plane;
- short-lived credentials for databases, message queues and artifact repositories.
When a Java-layer vulnerability is unavoidable, these controls decide how far a single RCE can ultimately go.
Closing
In 2002, researchers were still working out how to fool the bytecode verifier and the Applet sandbox. After 2016, attention turned to Java Serialization, JNDI and enterprise middleware. And by 2026, the focus has extended to the semantic difference between characters and bytes, the pre-authentication seams in middleware, and in-memory residence inside the JVM runtime.
On the surface, the vulnerability types have rotated many times over, but the core question has stayed remarkably stable:
Who is interpreting untrusted input, which dynamic capabilities get invoked during that interpretation, and what privileges are obtained in the end.
Java's strengths are maturity, compatibility and extensibility — and its security difficulties come from the very same place: a great many legacy mechanisms, dynamic capabilities and enterprise components piled up together over years. Doing Java security well today means neither fixating on a single CVE nor expecting a JDK upgrade to solve everything. What truly needs managing is the complete data path, the runtime classpath, the interpretation differences between components, and the privilege boundary after code executes.
That is the clearest thread the past two-plus decades of Black Hat Java talks have left behind.