Oracle's Secure Coding Guidelines for Java SE are the canonical rule set for Java code, yet most teams have never read them and many of their rules were written for a sandbox that no longer exists. The document, now at version 11.0 (last updated June 2025), groups more than 70 numbered guidelines into ten sections, from Fundamentals to Access Control, and covers problems that frameworks do not solve for you: deserialisation filtering, defensive copying of mutable state, integer overflow in limit checks, XML parser hardening and native code wrappers. This guide explains which sections matter most for server-side Java in 2026, how to turn them into code review rules and static analysis, and which Access Control guidance became legacy when the Security Manager was permanently disabled in Java 24. It is for Java developers, reviewers and application security engineers. Spring-specific controls are in the Java and Spring Security Guide.
Key takeaways
- Use the guideline IDs as a shared vocabulary. IDs such as SERIAL-6, MUTABLE-3 and INJECT-5 make code review comments, SAST rule mappings and training precise and checkable.
- Serialisation is still the highest-impact section. SERIAL-6 "Filter untrusted serial data" calls an allowlist of safe classes "the most secure approach", and JDK 17 added context-specific filters through a JVM-wide filter factory (JEP 415).
- Mutability bugs are security bugs. MUTABLE-2 and MUTABLE-3 require copies of mutable outputs and inputs; without them, a caller can change a value after it has been validated (time of check to time of use).
- Section 9 is largely legacy. The guidelines state the Security Manager was deprecated in Java 17 and permanently disabled in Java 24, so guidelines 9-1 to 9-15 apply only to earlier releases; isolation now comes from processes, containers and modules.
- Several rules defend against denial of service, not just compromise. DOS-1, DOS-3 and DOS-5 cover zip bombs, XML entity expansion, overflow in size checks and hash-collision attacks on untrusted map keys.
What the guidelines are and how they are organised
The Oracle document is a set of principles with concrete Java examples, maintained alongside the JDK. Each guideline has a section number and a mnemonic ID (for example 6-3 / MUTABLE-3). The structure is:
| Section | ID prefix | Covers | Relevance for server-side Java today |
|---|---|---|---|
| 0 Fundamentals | FUNDAMENTALS-0 to 8 | Design principles: obviously no flaws, restrict privileges, trust boundaries, encapsulation, securing third-party code | High: the basis for design review |
| 1 Denial of Service | DOS-1 to 5 | Disproportionate resource use, resource release, overflow in limit checks, exception handling, untrusted hash keys | High for any service parsing client input |
| 2 Confidential Information | CONFIDENTIAL-1 to 3 | Sensitive data in exceptions, logs and memory | High |
| 3 Injection and Inclusion | INJECT-1 to 9 | Formatting, dynamic SQL, HTML and XML generation, command lines, XML inclusion, BMP files, Swing HTML, interpreting untrusted code, floating point values | High |
| 4 Accessibility and Extensibility | EXTEND-1, 4 to 6 | Limiting accessibility and extensibility, ClassLoader exposure, superclass effects | Medium to high for libraries |
| 5 Input Validation | INPUT-1 to 4 | Validating inputs and outputs of untrusted objects, wrapping native methods | High |
| 6 Mutability | MUTABLE-1 to 12 | Immutability, defensive copies, public statics, modifiable collections | High for libraries and shared models |
| 7 Object Construction | OBJECT-1, 4, 5 | Constructor exposure, overridable calls in constructors, cloning | Medium |
| 8 Serialisation and Deserialisation | SERIAL-1 to 4, 6 | Avoiding serialisation for sensitive classes, guarding data, deserialisation as construction, filtering | Very high |
| 9 Access Control | ACCESS-1 to 15, plus 9-16 to 9-20 | Permission checks, doPrivileged, caller-sensitive APIs, isolation of unrelated code | Mostly legacy after Java 24, except module and class loader isolation |
Some IDs (EXTEND-2, EXTEND-3, OBJECT-2, OBJECT-3, SERIAL-5) were moved into section 9 as guidelines 9-16 to 9-20, so older references to those IDs now point elsewhere. An appendix, "Defensive use of the JNI", covers native code.
Serialisation and deserialisation (section 8)
Java native serialisation reconstructs object graphs from bytes, running code in readObject and related methods of any class on the classpath that the stream names. SERIAL-3 tells developers to "view deserialization the same as object construction", meaning every invariant and security check in a constructor must also run on deserialisation (SERIAL-4). SERIAL-1 says to avoid serialisation for security-sensitive classes, and SERIAL-2 to guard sensitive data in serialised form.
Filtering untrusted serial data (SERIAL-6)
- Allowlist, not blocklist: the guidelines state an allowlist of safe classes that rejects everything else is the most secure approach; reject lists must also cover subclasses and fall behind new gadget chains.
- Inventory per context: list what each component or library serialises and deserialises, and build a filter for each context.
- JVM-wide factory (Java 17 and later): JEP 415 adds
jdk.serialFilterFactoryandObjectInputFilter.Config.setSerialFilterFactory(), so a factory can choose a filter per stream. A process-wide pattern can be set withjdk.serialFilter, for exampleexample.*;java.base/*;!*, where!*rejects everything not allowed. - Older JDKs: filter support was added in CPU releases 8u121, 7u131 and 6u141; RMI supports filters for remote invocations.
- Better still, avoid it: for data crossing a trust boundary, use a data format such as JSON with explicit schema binding and no polymorphic type information taken from the input.
INJECT-8 adds the related risk of "interpreting untrusted code": javax.script, XSLT extensions, XMLDecoder-style bean persistence, RMI and LDAP codebase loading and JNDI lookups. It recommends keeping java.rmi.server.useCodebaseOnly and com.sun.jndi.ldap.object.trustURLCodebase at their safe settings, setting the jdk.jndi.*.factoriesFilter properties and applying deserialisation filters to JNDI. Deeper patterns are in the Secure Deserialisation Guide.
Injection and XML handling (section 3)
| Guideline | Rule | Concrete practice |
|---|---|---|
| INJECT-2 Avoid dynamic SQL | Bind, do not concatenate | PreparedStatement or CallableStatement with setString() and similar; prefer a higher-level data library. See the SQL Injection Prevention Guide. |
| INJECT-3 XML and HTML generation requires care | Encode untrusted data on output | Allowlist safe characters, use a tested library to build markup, take extra care in JSP |
| INJECT-4 Avoid untrusted data on the command line | Keep data out of argument strings | Pass data Base64-encoded, through a temporary file or an inherited channel |
| INJECT-5 Restrict XML inclusion | Most restrictive parser configuration | Disallow external entities or disable DTDs; reduce privileges of parsing code |
| INJECT-8 Interpreting untrusted code | Sandbox in a separate process | Avoid script engines and XSLT on untrusted input |
| INJECT-9 Exceptional floating point values | Reject NaN and infinity where they break logic | Check Double.isNaN and isInfinite on untrusted numbers |
XML parser settings for INJECT-5 and DOS-1
The OWASP XML External Entity Prevention Cheat Sheet gives the matching JAXP settings. For DocumentBuilderFactory, set http://apache.org/xml/features/disallow-doctype-decl to true; if DTDs are needed, set external-general-entities, external-parameter-entities and load-external-dtd to false together. For StAX XMLInputFactory, set SUPPORT_DTD and IS_SUPPORTING_EXTERNAL_ENTITIES to false, since their defaults are not safe. On JDK 22 and later the built-in parsers honour jdk.xml.dtd.support (deny or ignore). Use newDefaultInstance() to get the built-in parser predictably, and fail closed if a setting is rejected. DOS-1 adds XMLConstants.FEATURE_SECURE_PROCESSING and parser limits against entity expansion. See the XXE Guide.
Denial of service and confidential information (sections 1 and 2)
- DOS-1 disproportionate resources: check image dimensions and input sizes before loading; limit decompressed size rather than trusting compressed size or metadata (zip bombs); validate untrusted lengths and offsets before allocating.
- DOS-3 overflow in limit checks: write
current > max - extrarather thancurrent + extra > max; useMath.addExactandmultiplyExact(Java 8 and later), andObjects.checkIndex,checkFromIndexSizeandcheckFromToIndex(Java 9 and later). Remember-Integer.MIN_VALUEis still negative. - DOS-5 untrusted hash keys: attacker-chosen keys in
HashMaporHashSetcan force collisions; preferComparablekeys and bound collection sizes. - CONFIDENTIAL-1 exceptions: catch internal exceptions and rethrow only known information; even an exception type such as
FileNotFoundExceptionreveals whether a file exists; never show stack traces to users. - CONFIDENTIAL-2 logging: keep passwords and identifiers such as national insurance or social security numbers out of logs, including logs from low-level libraries that log their input. The same applies to API keys and tokens, which are non-human identity credentials.
- CONFIDENTIAL-3 memory: consider purging highly sensitive data from memory after use, which is why APIs take passwords as
char[]rather thanString.
Mutability, extensibility and object construction (sections 4, 6 and 7)
These sections matter most for libraries, SDKs and shared domain models, where the caller is not trusted to leave objects alone.
| Guideline | Failure it prevents | Practice |
|---|---|---|
| MUTABLE-1 Prefer immutability for value types | Shared state changed unexpectedly | Records and final fields for value objects |
| MUTABLE-2 Create copies of mutable output values | Callers change internal state through a returned reference | Return new Date(date.getTime()) or an unmodifiable view |
| MUTABLE-3 Create safe copies of mutable and subclassable input values | Time-of-check to time-of-use: value changed after validation | Copy first, then validate and use; deep-copy collections of mutable elements; do not trust clone() on non-final input types |
| MUTABLE-9 to 12 Public statics and collections | Global state tampering | Public static fields final, values truly constant, no exposed modifiable collections |
| EXTEND-1 Limit accessibility | Internal APIs used as attack surface | Least visibility; module exports only for public API |
| EXTEND-5 Limit extensibility | Malicious subclasses override security behaviour | final classes or sealed hierarchies (Java 15 and later) with permits |
| OBJECT-4 No overridable calls in constructors | Subclass observes a partially built this | Applies equally to clone, readObject and readObjectNoData |
| OBJECT-5 Defend against cloning of non-final classes | Clones bypass constructor checks | Make classes final or override clone safely |
Native code and the JNI (INPUT-3 and Appendix A)
Native code runs outside the JVM's memory safety. INPUT-3 says to declare native methods private and expose them only through a public Java wrapper that validates arguments and clones mutable arrays, with overflow-safe bounds checks such as offset < 0 || len < 0 || offset > data.length - len. The guidelines' appendix, "Defensive use of the JNI", goes further; review it before approving any new native library, and treat third-party native libraries as part of your software supply chain under FUNDAMENTALS-8 "Secure third-party code".
Access control after the Security Manager (section 9)
The guidelines now say the Security Manager was deprecated in Java 17 and permanently disabled in Java 24, and that guidelines 9-1 to 9-15 (for example ACCESS-3 on doPrivileged and ACCESS-8 to ACCESS-11 on caller-sensitive APIs) apply only to earlier releases. What this means in practice:
- Do not design new sandboxes on the Security Manager: run untrusted code in a separate process or container with operating system controls, which INJECT-8 also recommends.
- Keep module and class loader isolation: guidance on not exposing
ClassLoaderinstances (EXTEND-4) and isolating unrelated code still applies on current JDKs. - Legacy estates: applications still on Java 8, 11 or 17 that rely on a policy file need the full section 9 review, and a migration plan before upgrading to Java 24 or later.
Putting the guidelines into practice
- Select the rules that apply: for a typical service, sections 0, 1, 2, 3, 5 and 8 in full; sections 4, 6 and 7 for shared libraries; section 9 only for legacy runtimes.
- Map rules to tooling: map each selected guideline ID to a SAST rule in your analyser (most Java analysers cover dynamic SQL, XXE parser settings, unsafe deserialisation and command execution), and mark the ones that need manual review, such as MUTABLE-3 and SERIAL-4.
- Set JVM-level defaults first: a
jdk.serialFilteror filter factory in report-and-tune mode, hardened XML factory helpers in a shared library, and JNDI factory filters. Roll out to non-production, review rejected classes in logs, then enforce. - Put the IDs in the review checklist and pull request template, so reviewers cite "SERIAL-6" rather than "looks unsafe".
- Train on the high-impact sections using your own past findings as examples.
- Roll back safely: serial filters and parser settings are configuration; keep previous values so a rejected legitimate class can be added to the allowlist quickly instead of disabling the filter.
Small teams can start with steps 3 and 4. Large organisations should own the shared hardened helpers (XML factories, filter factory, safe process launching) centrally, as FUNDAMENTALS-2 "Avoid duplication" suggests, so fixes land once. For broader practice see the Secure Coding Guide.
Detection and measurement
- Runtime: log deserialisation filter rejections (the filter's status) and alert on rejected classes from external inputs, which often indicate probing.
- Static: SAST findings for
ObjectInputStreamwithout a filter,Statement.executewith concatenation, unsafe XML factories andRuntime.execwith untrusted strings. - Dependencies: track libraries known for gadget chains and older JDKs without filter support.
| Metric | Target |
|---|---|
| Services running with a deserialisation filter or factory configured | 100% of services that deserialise |
| XML parsers created outside the hardened helper | Zero |
| Dynamic SQL findings open beyond SLA | Zero |
| Selected guideline IDs mapped to an automated rule or review step | 100% |
| Applications relying on the Security Manager without a migration plan | Zero |
Common failure patterns
- Blocklist deserialisation filters: new gadget classes and subclasses slip through; SERIAL-6 prefers allowlists.
- Hardening one XML factory but not the others: DOM is fixed while StAX or
TransformerFactoryremain at unsafe defaults. - Validate then copy: checking a mutable argument and then storing it lets the caller change it afterwards; MUTABLE-3 requires copy then validate.
- Stack traces in API responses: framework default error pages leak internals, against CONFIDENTIAL-1.
- Size checks that overflow:
offset + len > lengthwraps negative and passes, against DOS-3. - Relying on a Security Manager policy on new JDKs: it is disabled from Java 24, so the protection silently disappears on upgrade.
Framework mapping
| Framework | Reference | Guideline sections |
|---|---|---|
| NIST SP 800-53 Rev. 5 | SA-8 Security and Privacy Engineering Principles | 0 Fundamentals |
| NIST SP 800-53 Rev. 5 | SI-10 Information Input Validation | 3 Injection, 5 Input Validation, 8 Serialisation |
| NIST SP 800-53 Rev. 5 | SI-11 Error Handling | 2 Confidential Information |
| NIST SP 800-53 Rev. 5 | SC-39 Process Isolation | INJECT-8, section 9 replacement |
| NIST SP 800-53 Rev. 5 | SA-11 Developer Testing; SA-15 Development Process, Standards, and Tools | SAST mapping, review checklist |
| NIST CSF 2.0 | PR.PS-06 | Secure development practice across the SDLC |
Practitioner checklist
- Read sections 0, 3 and 8 of the Oracle guidelines with your lead developers and agree which IDs you adopt.
- Configure a deserialisation allowlist (
jdk.serialFilteror a JEP 415 filter factory) for every service that uses Java serialisation, or remove serialisation. - Ship hardened XML factory helpers covering DOM, SAX, StAX and transformers, and block direct factory use in review.
- Use
PreparedStatementor a data library for all SQL; no concatenation. - Apply copy-then-validate to mutable inputs and return copies of mutable internals in shared libraries.
- Use overflow-safe arithmetic (
Math.addExact,Objects.checkFromIndexSize) in size and offset checks. - Sanitise exceptions and keep secrets, tokens and personal data out of logs.
- Wrap native methods and review new JNI libraries against the appendix.
- Plan migration away from any Security Manager dependency before moving to Java 24 or later.
Standards and references
- Oracle: Secure Coding Guidelines for Java SE
- OpenJDK: JEP 415 Context-Specific Deserialization Filters
- OWASP: XML External Entity Prevention Cheat Sheet
- OWASP: Deserialization Cheat Sheet
- NIST: SP 800-218 Secure Software Development Framework 1.1
- NIST: SP 800-53 Rev. 5
Related NHI Mgmt Group resources: Java and Spring Security Guide · Secure Deserialisation Guide · XXE Guide · Secure Coding Guide