JDK-8305968 : Integrity by Default
  • Type: JEP
  • Component: not defined
  • Priority: P2
  • Status: Draft
  • Resolution: Unresolved
  • Submitted: 2023-04-13
  • Updated: 2024-08-23
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
Summary
-------

Developers expect that their code and data is protected against use that
is unwanted or unwise.  The Java Platform, however, contains unsafe APIs
that can undermine this expectation, thereby damaging the correctness,
maintainability, scalability, security, and performance of applications.
Going forward, we will restrict the unsafe APIs so that, by default,
libraries, frameworks, and tools cannot use them.  Application authors
will have the ability to override this default.


What is integrity?
------------------

The Oxford English Dictionary defines “integrity” as “the state of being
whole and undivided; the condition of being sound in construction.”

In the context of a computer program, integrity means that the
constructs from which we build the program — and ultimately the program
itself — are both whole and sound.  Such constructs, whether they are
low-level language facilities such as `for` loops or higher-level
components such as classes or modules, have both specifications and
implementations.  Integrity thus requires two things of a computing
construct:

  - Its specification must say everything that needs to be said in order
    to make effective use of the construct (wholeness), and

  - Its implementation must satisfy its specification (soundness).

In more familiar terms, we say that a computing construct has integrity
if, and only if, its specification is _complete_ and its implementation
is _correct_ with respect to the specification.

For example, the specification of Java arrays says that an array can
only be accessed within the bounds set for it upon creation.  This
constraint is guaranteed by the JVM, which raises an exception if it is
violated.

The specification of Java arrays contains many other statements; e.g.,
that the length of an array never changes, that the first element of an
array always has the index zero, and that accessing an array element
after setting that element to some value returns exactly the same value
(modulo concurrency).  The JVM guarantees all of these statements —
hence arrays are correct.  Taken together, moreover, these statements
capture all that we need to know in order to reason about any particular
use of arrays — hence arrays are complete.  We do not need to wonder,
e.g., whether an array might silently increment all its elements at
midnight on alternate Wednesdays, because its specification says nothing
about midnight, or Wednesdays, and in fact its specification implies
that this absurd situation cannot happen.  Thus we can say that Java
arrays have integrity.

(Integrity has practical limits, of course; the JVM cannot prevent
native code or external debuggers or [cosmic rays] from modifying array
content.  When we speak of integrity here, we mean integrity within the
context of the Java Platform.)

[cosmic rays]: https://en.wikipedia.org/wiki/ECC_memory

The Java Platform contains not just arrays but many other useful
constructs, in both the language and in its built-in libraries.  All of
these constructs have both specifications and implementations, which
taken together give the Platform itself a specification and an
implementation.  We intend, naturally, that the overall Platform have
integrity: Its specification says all that needs to be said in order to
reason effectively about its use (completeness), and its implementation
behaves according to its specification (correctness).  The integrity of
the Platform enables us to reason about the correctness of our own code,
starting from the specifications of the Platform's constructs.


## Benefits of integrity

The Java Platform's integrity underpins many of its key benefits.

  - The Platform specifies that variables, fields, and arrays are
    initialized before use, thus a program's initial state is well
    defined.

  - The Platform specifies [automatic storage management](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)),
    thus a program never suffers from [use-after-free errors](https://en.wikipedia.org/wiki/Dangling_pointer).

  - The Java language and the Java Virtual Machine are specified so as
    to guarantee [type
    safety](https://en.wikipedia.org/wiki/Type_safety), thus a program
    cannot perform invalid operations on data, such as treating a
    `String` as a `Socket`.

  - The Platform API (as of Java 20) does not allow threads to be
    stopped arbitrarily, thus a multi-threaded program never sees
    objects in an [inconsistent state](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/doc-files/threadPrimitiveDeprecation.html).

Without integrity, we cannot rely upon any of these valuable properties.


## Integrity via encapsulation

The Java language provides built-in constructs which enable us to build
our own constructs at higher and higher levels of abstraction, by hiding
unnecessary detail.  We compose statements into methods, methods and
fields into classes, classes into packages, packages into modules, and
finally modules into entire programs.

Abstraction enables us to control program complexity: We can show that
the implementation of a higher-level construct meets its specification
by reasoning solely from the specifications of the lower-level
constructs upon which it is built; there is no need to consider the
implementation details of the lower-level constructs, nor the
specifications of any other constructs.  Likewise, users of the
higher-level construct need only refer to the specifications of that
construct, and of any other constructs that they use, when reasoning
about their own code; there is no need to consider the implementation
details of the higher-level construct, nor the specifications of any
other constructs.  Ultimately, we can, in principle, show that an entire
program meets its specification via such reasoning.

For all of this to work requires that our higher-level constructs
themselves have integrity: They must be complete and correct.  A key
tool for achieving that is encapsulation.

For example, suppose we want to build a counter abstraction that is
always even, never odd.  Imagine that the Java language had no
encapsulation, so that all fields and methods could be accessed from
anywhere, as if everything were `public`.  We might declare an
`EvenCounter` class, like so:

```
/**
 * Specification:
 *   - value() initially returns zero
 *   - incrementByTwo() increments value() by two
 *   - decrementByTwo() decrements value() by two
 *   - value() is always even, never odd
 */
/*public*/ final class EvenCounter {
    /*public*/ int x = 0;
    /*public*/ int value() { return x; }
    /*public*/ void incrementByTwo() { x += 2; }
    /*public*/ void decrementByTwo() { x -= 2; }
}
```

We can easily show that the `EvenCounter` class, in isolation, meets its
specification, thus it is correct.  Its specification, however, is not
complete: It does not say everything that needs to be said in order to
make effective use of the class.  That is because code external to the
class can set the `x` field to an odd number at any time, thereby
causing the `value()` method to violate the class's specification.  To
show that a use of the class is correct we must analyze every line of
the entire program to ensure that no code external to the class modifies
this field.  Rather than simple local reasoning about each such use,
complex global reasoning is required.  It is as if the specification of
the `EvenCounter` class includes the additional requirement that

```
 *   - No code external to this class modifies the x field
```

With the actual Java language, of course, there is no need for this
complexity since the language provides encapsulation constructs — the
`private` and `public` keywords — which allow us to protect data from
intentional or unintentional modification.

```
public final class EvenCounter {
    private int x = 0;
    public int value() { return x; }
    public void incrementByTwo() { x += 2; }
    public void decrementByTwo() { x -= 2; }
}
```

Here we use the `private` keyword to protect the `x` field from external
access.  The `private` keyword has integrity: Its specification says
that a private field can be modified only by code in the same class, and
the Java compiler and the JVM guarantee this specification throughout
the program.  Making the `x` field private thus obviates the need to
analyze the entire program when reasoning about the correctness of any
use of the `EvenCounter` class.  In other words, local reasoning about
each such use is sufficient.  The class's original specification, above,
is thus complete; we already know that the class is correct with respect
to that specification, thus the class has integrity.

Abstraction enables us to create higher-level computing constructs;
encapsulation enables us to imbue those constructs, and ultimately
entire programs, with integrity.  This provides tremendous value.

  - _Correctness_ — The correctness of a program can rest upon the
    integrity of the `EvenCounter` class, in particular the fact that
    the value in an instance is always even. An application could,
    e.g., use `EvenCounter` to track business activity where every
    purchase needs to match a sale, resulting in an even number of
    transactions.  Using encapsulation to imbue the class with integrity
    ensures that correctness cannot be undermined by code external to
    the class.

  - _Maintainability_ — Encapsulation protects code as it evolves.  We
    assume that `private` fields and methods are implementation details,
    able to be safely changed without breaking clients. The private
    field of the `EvenCounter` class cannot be accessed by client code,
    thus we can change it at will so long as we preserve correctness.
    We could, e.g., rename the field, or change its type to `Integer`.
    Encapsulation gives classes the integrity required to enable
    independent internal evolution.

  - _Scalability_ — Encapsulation is a cornerstone of [programming in the large](https://en.wikipedia.org/wiki/Programming_in_the_large_and_programming_in_the_small)
    because it ensures the integrity that enables local reasoning about
    the behavior of computing constructs. Programs can be built from
    independently-developed components that interact only through their
    public APIs and behave according to their specifications. This
    allows not just individual programs but the entire Java ecosystem to
    scale as collections of independent interoperating components.

  - _Security_ — Encapsulation is essential for any kind of robust
    security.  Suppose, e.g., that a class in the JDK restricts a
    sensitive operation:

    ```
    if (isAuthorized())
        doSensitiveOperation();
    ```

    The restriction is robust only if we can guarantee that
    `doSensitiveOperation()` is only ever invoked after a successful
    `isAuthorized()` check.  If we declare `doSensitiveOperation()` as
    `private` in its declaring class then we know that no code in any
    other class can directly invoke that method; in other words, the
    declaring class has integrity with respect to that method.  Code
    reviewers thus need only ensure that all invocations of the method
    within the declaring class are preceded by an `isAuthorized()`
    check; they can ignore all the other code in the program.

  - _Performance_ — In the Java runtime, numerous optimizations can
    benefit from the integrity ensured by encapsulation.  The JVM can,
    e.g., perform [constant
    folding](https://en.wikipedia.org/wiki/Constant_folding)
    optimizations when it determines that the value of a `private` field
    never changes.  Going further, a tool such as `jlink` could remove
    unused `private` methods at link time to reduce image size and class
    loading time.


## Undermining integrity

Encapsulation is a key tool for establishing integrity.  It underpins
correctness, maintainability, scalability, security, and performance.
There are, however, four APIs in the JDK which can circumvent it.

  - The
    [`AccessibleObject::setAccessible(boolean)`](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/reflect/AccessibleObject.html#setAccessible(boolean))
    method in the
    [`java.lang.reflect`](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/reflect/package-summary.html)
    package enables _deep reflection_, which is reflection over fields
    and methods without regard to encapsulation boundaries.  This method
    was introduced in Java 1.2 to support object serialization and
    deserialization, but in practice any code can use it to invoke the
    `private` methods of any class, read and write the `private` fields
    of any object, and even write `final` fields.

  - The `sun.misc.Unsafe` class includes methods that can access
    `private` methods and fields, and write `final` fields, similar to
    deep reflection.

  - The [Java Native Interface](https://docs.oracle.com/en/java/javase/22/docs/specs/jni/index.html)
    (JNI) allows native code to interact with Java objects without
    regard to encapsulation boundaries.  Native code can access
    `private` methods and fields, and write `final` fields, similar to
    deep reflection.

  - The [Instrumentation API](https://docs.oracle.com/en/java/javase/22/docs/api/java.instrument/java/lang/instrument/package-summary.html)
    allows components called *agents* to modify the bytecode of any
    method in any class.  An agent can, e.g., rewrite the
    `incrementByTwo` method of the `EvenCounter` class to log the old
    value of `x` to a file before incrementing it.

We refer to these APIs as _unsafe_ because they violate the integrity of
the Java language's encapsulation constructs, thereby violating the
integrity not only of the Platform itself but of every component and
program built on top of it.  The `private` field in an `EvenCounter`
object could, e.g., be modified from outside the class via deep
reflection, `sun.misc.Unsafe`, or native code, resulting in an odd
value, violating the class's specification.  The `public` methods of
`EvenCounter` could be redefined by an agent to increment the `private`
field by one instead of two, again resulting in an odd value.

The fact that the language's encapsulation constructs lack integrity
destroys the ability to reason locally about a program's correctness.
To show that a use of an encapsulated component is correct we must
analyze every class on the class path, on the module path, or loaded
dynamically, and either rule out the use of unsafe APIs or else ensure
that their use does not violate the component's specification.  This
analysis is not practical, thus any code that relies on the evenness of
`EvenCounter` objects for its own correctness may behave incorrectly,
and any client of that code may behave incorrectly, and so on.

Even if a library uses an unsafe API with good intentions, and does not
explicitly violate any other component's specification, it could still
enable specification violations in an application that uses it.  A JSON
serialization library could, e.g., deserialize an `EvenCounter` object
by using deep reflection to set the value of the object's private field,
bypassing `EvenCounter`'s public API.  This, in itself, does not violate
the specification of the `EvenCounter` class.  If the application does
not, however, take care to explicitly validate that its JSON input does
not contain an odd number, then reading such input will result in an odd
`EvenCounter`.  The serialization library does not explicitly violate
`EvenCounter`'s specification, but by circumventing `EvenCounter`'s
defense mechanism — its encapsulation — it makes it vulnerable to
indirect specification violations.

This problem is especially serious with security-sensitive components.
A vulnerability in a library that uses an unsafe API jeopardizes the
integrity of every component of the application, and could allow an
adversary to manipulate input to the application in a way that
undermines security.

The unsafe APIs in the JDK violate the integrity of language constructs
other than those related to encapsulation.  Constructs that access
arrays and objects, in particular, are specified so as to ensure [memory
safety](https://en.wikipedia.org/wiki/Memory_safety): An array cannot be
accessed beyond its bounds, and an object cannot be accessed after its
storage is reclaimed.  We have relied on the memory safety of the Java
Platform for decades, but it can be violated by the unsafe APIs, leading
to [undefined
behavior](https://en.wikipedia.org/wiki/Undefined_behavior) and even JVM
crashes.

  - JNI allows the execution of native code that can violate memory
    safety.  Native code can also produce a [byte
    buffer](https://docs.oracle.com/en/java/javase/22/docs/specs/jni/functions.html#newdirectbytebuffer)
    that wraps arbitrary memory locations, which means any Java code
    that [accesses the
    buffer](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/nio/ByteBuffer.html)
    can cause undefined behavior.

  - The Foreign Function & Memory API (FFM, [JEP
    454](https://openjdk.org/jeps/454)) allows the execution of native
    code that can violate memory safety.  The FFM API also allows Java
    code to create a [memory
    segment](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/foreign/MemorySegment.html)
    that wraps arbitrary memory locations.  Any Java code that accesses
    such a segment can cause undefined behavior.

  - The `sun.misc.Unsafe` class includes methods that can read and write
    arbitrary memory locations, both on and off the JVM's heap.  Thus an
    array can be accessed beyond its bounds, and an object's storage can
    be accessed long after it is reclaimed by the garbage collector — a
    classic use-after-free error.

The integrity of the Java Platform — and hence the correctness,
maintainability, scalability, security, and performance of our programs
— requires that we prevent encapsulation from being circumvented and
memory safety from being violated.  How can we square this with the
presence of the unsafe APIs, which are designed to offer library,
framework, and tool developers special superpowers for use in rare
situations in which there is no other way to solve a problem?  The
answer is that we must adopt integrity by default.


## Integrity by default

_Integrity by default_ means that every construct of the Java Platform
has integrity, unless overridden explicitly at the highest level of the
program.  That is, the developer of an application can choose to give up
selected kinds of integrity within the scope of that application; the
developer of a library, framework, or tool, however, cannot.  An
application developer can, e.g., choose to configure the Java runtime to
allow a serialization library to use unsafe APIs, knowingly acquiescing
to a loss of integrity because the library's functionality is
indispensable.  Without such explicit permission, however, that library
cannot, on its own, violate any aspect of Platform or application
integrity.

We have gradually been moving the Java Platform toward integrity by
default since JDK 9.  We have done so by selectively degrading or
gating the ability of the unsafe APIs to undermine integrity.  This
effort has three strands.

  - _JDK code is strongly encapsulated in modules._ By default, deep
    reflection cannot circumvent strong encapsulation.

  - _Unsafe APIs that are standard in the Java Platform are restricted._
    By default, Java code cannot circumvent encapsulation by using the
    Instrumentation API to redefine methods, or violate encapsulation or
    memory safety by using JNI or FFM to call native code.

  - _Unsafe APIs that are non-standard are removed when standard
    replacement APIs become available._ The replacement APIs are
    designed so that, by default, they cannot undermine integrity.


### Strong encapsulation: The antidote to deep reflection

JDK 9 introduced [*modules*](https://dev.java/learn/modules/) to the
Java language.  A module is a set of packages designed to work together
and intended for re-use.  If a package is _exported_ then its public
elements can be used outside the module; if a package is not exported
then its public elements can be used only inside the module.

Modules provide *strong encapsulation*, which means that reflection by
code outside of a module cannot access the private elements of any class
within the module.  That is, the `setAccessible` method respects module
boundaries.  If the public `EvenCounter` class, e.g., is declared in an
explicit module, then its private field `x` cannot be modified by deep
reflection initiated by code outside the module.


### <a id="Restrictions">Restrictions on standard unsafe APIs</a>

Most of the unsafe APIs — `setAccessible`, JNI, FFM, and Instrumentation
— continue to be supported in the Java Platform.  While they are rarely
used by application code directly, they are essential for a relatively
small number of libraries, frameworks, and tools whose core
functionality cannot be implemented any other way.  Examples include:

  - Frameworks for unit testing and dependency injection (DI) that use
    deep reflection to access `private` fields and methods of
    application classes;

  - Serialization libraries that use deep reflection to access `private`
    fields of application classes;

  - Mocking libraries that use the Instrumentation API to redefine
    methods of application classes;

  - Native wrapper libraries that use JNI to call `native` methods or
    FFM to invoke downcall method handles; and

  - Application Performance Monitoring (APM) tools that use agents to
    inject logging and performance counters into application code.

A component that uses an unsafe API violates the integrity of the Java
Platform: It introduces the possibility that encapsulation will be
circumvented or memory safety will be violated, thereby rendering the
specification of the Platform incomplete.  If the Platform has no
integrity then components built on top of it have no integrity, and
applications themselves have no integrity.  The policy of integrity by
default enshrines the idea that the developer of a library, framework,
or tool cannot unilaterally decide to violate integrity by using an
unsafe API.  That power — and the corresponding responsibility — belongs
solely to the application's developer (or perhaps deployer, on the
advice of the developer).  The application's developer answers to end
users for the behavior of the application; developers of libraries,
frameworks, and tools, by contrast, do not.

We cannot treat the mere inclusion of an unsafe-using library or
framework in an application as consent by the application's developer to
violate integrity.  The developer might not be aware that the component
uses an unsafe API.  The developer might not even be aware that the
component is present, since the component could be an indirect
dependency several layers removed from the application itself.  The
application developer must therefore explicitly configure the Java
runtime to allow selected components to use unsafe APIs.  If the runtime
is not suitably configured then using an unsafe API causes an exception
to be thrown.  In other words, use of unsafe APIs is restricted by default.

Various command-line options configure the Java runtime to allow the use
of unsafe APIs:

  - [`--add-opens`](https://openjdk.org/jeps/261#Breaking-encapsulation)
    allows code in specified modules to use `setAccessible` on the
    `private` elements of other modules.  A related option,
    `--add-exports`, allows code in specified modules to access `public`
    elements of otherwise unexported packages.

  - [`--enable-native-access`](https://openjdk.org/jeps/454#Safety)
    allows code in specified modules to use FFM to create arbitrary
    memory segments and to find and invoke native code.  In the future,
    this option will also be required to enable the use of JNI.

  - [`-javaagent`](https://docs.oracle.com/en/java/javase/22/docs/api/java.instrument/java/lang/instrument/package-summary.html)
    allows an agent to use the Instrumentation API.  A related option,
    `-XX:+EnableDynamicAgentLoading`, allows tools to load agents
    dynamically.

Application developers can specify these options in multiple ways:

  - Pass them directly to the `java` launcher in the script that starts
    the application,

  - Pass them indirectly to the `java` launcher by setting the
    environment variable
    [`JDK_JAVA_OPTIONS`](https://docs.oracle.com/en/java/javase/22/docs/specs/man/java.html#using-the-jdk_java_options-launcher-environment-variable),

  - Place them in an [argument file](https://docs.oracle.com/en/java/javase/22/docs/specs/man/java.html#java-command-line-argument-files) that is passed to the `java` launcher (e.g., `java @config`) by a script or an end user,

  - Place corresponding manifest entries in the application's executable
    JAR file (`Add-Opens`, `Add-Exports`, `Enable-Native-Access`, and
    `Launcher-Agent-Class`; there is no manifest entry corresponding to
    `-agentlib`)

  - Configure the runtime programmatically as described in [Embracing integrity by default](#Embracing-integrity-by-default).

No matter how they are specified, these options configure the Java
runtime when it starts, enabling the JVM to determine how integrity will
be undermined and which optimizations should be enabled or disabled.
These options also make it easy for application developers to audit the
use of unsafe APIs and understand the risks posed to correctness,
maintainability, scalability, security, and performance.  If none of
these options is used then the application developer can be certain that
neither the application nor its dependencies violate the integrity of
the Platform, the application's dependencies, or the application itself.

### Adapting to restrictions on unsafe APIs

Most of the unsafe APIs in the Java Platform were in use for years before they were deemed unsafe, so it is not practical to restrict them without notice: applications would fail unexpectedly. In addition, configuring the Java runtime to allow the use of unsafe APIs by libraries, frameworks, and tools is not part of the traditional developer experience. To alert application developers to the need to configure the Java runtime, we restrict the use of a pre-existing unsafe API in a gradual fashion:

- In an initial JDK release, the API can be used as normal.
- In a later JDK release, the API can be used, but doing so produces a warning. The warning identifies the library that used the API and frames the use as "illegal". The warning also explains how to configure the Java runtime to allow the use, e.g., with `--add-opens`. Only the first use of the API by a particular module causes a warning; further use by code in the same module does not cause further warnings.
- Eventually, in another JDK release, the API cannot be used by default. Calling the API causes an exception to be thrown, unless the Java runtime has been configured to allow the use.

This process typically takes a few years, during which time the JDK offers a temporary command-line option that lets the application developer "dial up" or "dial down" the process. For example, `--add-opens` had a temporary counterpart of [`--illegal-access`](https://openjdk.org/jeps/261#Relaxed-strong-encapsulation). The temporary option has three settings:

- `allow` (or `permit`) -- allow use of the API, with no warnings.
- `warn` -- allow use of the API, with warnings.
- `deny` -- disallow use of the API, and throw an exception.

Typically, the temporary option defaults to `allow` in the initial JDK release, then `warn` in a later JDK release, and eventually `deny`. Application developers can "dial up" the option to `deny` at any time, simulating the long-term behavior planned for the Java runtime. In contrast, the ability to "dial down" the option is limited: when the default is `warn`, the option can be set to `allow`, but once the default is `deny`, the option can only be set to `warn`, not `allow`. After the default has been `deny` for some time, the temporary option is removed.

### Removing non-standard unsafe APIs

The `sun.misc.Unsafe` class includes methods that perform a variety of
low-level operations without any safety checks.  Since JDK&nbsp;9 we
have been adding standard APIs that offer safer replacements for this
functionality.  The low-level manipulation of objects in the JVM's heap,
e.g., can now be done more safely via the
[`VarHandle`](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/invoke/VarHandle.html)
API, and manipulation of data in off-heap memory can now be done more
safely via FFM's
[`MemorySegment`](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/foreign/MemorySegment.html)
API.

We have already deprecated for removal and, later, removed some elements
of `sun.misc.Unsafe` which now have standard API replacements.  We will
continue to do so in future releases.  Ultimately, we will deprecate
`sun.misc.Unsafe` itself for removal, and then remove it.


### Embracing integrity by default

Libraries, frameworks, and tools can relieve application developers of
some of the effort of configuring the Java runtime in many situations.

  - Developers of dependency injection frameworks can ask application
    developers to grant access to the application's `private` fields and
    methods directly in the code.  One approach is to ask application
    developers to open the packages of their modules to the framework
    module by placing, e.g.,

    ```
    opens com.example.app to org.framework;
    ```

    in their module declarations.  Deep reflection can access every
    element in an open package, even `private` elements.  A framework
    can, if necessary, transfer its access rights to another component
    via
    [`Module::addOpens`](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/Module.html#addOpens(java.lang.String,java.lang.Module)).

    A better approach is to ask application developers to create
    [method-handle lookup
    objects](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/invoke/MethodHandles.Lookup.html)
    and pass them to the framework; e.g.,

    ```
    Framework.grantAccess(MethodHandles.lookup());
    ```

    A lookup object grants access to the `private` elements accessible
    to the code that created it, so the framework can use the lookup
    object to perform deep reflection on application code without any
    application packages being open.

  - Serialization libraries have caused many security vulnerabilities by
    using deep reflection to access private fields of application
    classes.  In general, it is a mistake for libraries to serialize and
    deserialize an object without the cooperation of the object's class.
    Objects such as strings, records, enums, and collections are easy to
    serialize and deserialize because their classes provide `public`
    accessors and constructors.  For other objects, serialization
    libraries should specify protocols by which classes can expose their
    state during serialization and accept and validate new state during
    deserialization.  For this to work, application developers may need
    to grant access to classes in non-exported packages by opening
    packages or passing lookup objects.

    Some classes already take responsibility for their own serialization
    and deserialization by implementing the
    [`java.io.Serializable`](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/io/Serializable.html)
    interface.  Serialization libraries can take advantage of that by
    invoking the `writeObject` and `readObject` methods of such classes
    via the
    [`sun.reflect.ReflectionFactory`](https://github.com/openjdk/jdk/blob/master/src/jdk.unsupported/share/classes/sun/reflect/ReflectionFactory.java)
    class, which is [supported for this
    purpose](https://openjdk.org/jeps/260#Critical-internal-APIs-not-encapsulated-in-JDK-9).

    In the long term, we expect the Java Platform to offer [better
    serialization](https://openjdk.org/projects/amber/design-notes/towards-better-serialization).

  - Unit-testing frameworks and mocking libraries can integrate with
    build tools such as Maven and Gradle to configure the Java runtime
    automatically.  Build tools could, e.g., start test runs with
    options necessary to circumvent encapsulation `(--add-opens`,
    `--add-exports`), patch modules (`--patch-module`), and install
    agents (`-javaagent`).

More elaborate frameworks and applications that wish to control the initialization and bootstrapping of the runtime and/or of components can programmatically grant code permission to use unsafe APIs: 

  - An application with a custom native launcher that loads the JVM through the [JNI invocation API](https://docs.oracle.com/en/java/javase/22/docs/specs/jni/invocation.html) can programmatically pass the JVM options such as `--add-opens`, `--enable-native-access`, or `-javaagent`.

  - A framework or an application that dynamically loads component modules can allow the component to use unsafe APIs with the `addExports`, `addOpens`, and `enableNativeAccess` methods of the [`ModuleLayer.Controller` API](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/ModuleLayer.Controller.html).


### Integrity beyond the Java Platform

Java code can use standard facilities of the Platform to reach outside
the Java runtime and violate integrity.  Java code can, e.g., alter the
content of a class file in the file system before the class is loaded.
However, a good principle in matters of integrity is that

> The integrity of components is best enforced by the infrastructure
  that provides them.

The integrity of the file system and its content is the responsibility
of the operating system, not the Java runtime.  The OS or, if
appropriate, an OS-level container, should always be configured so as to
protect the integrity of the Java runtime's files and memory, and the
integrity of the application’s files, regardless of the measures taken
by the Java runtime to protect its own integrity and that of the
application it is running.


## Why now?

The Java ecosystem has managed just fine without strong encapsulation or
restrictions on unsafe APIs for nearly three decades.  Why are we now
adopting integrity by default, which adds overhead for some library,
framework, tool, and application developers?

The answer is that, in recent years, both the JDK and the environment in
which Java applications run have changed.

  - _Correctness_ — Historically, the Java runtime has been able to
    ensure the integrity of many low-level constructs because they were
    implemented in native code, beyond the reach of the unsafe APIs.
    However, more and more of the Java runtime itself is being written
    or rewritten in Java.  This means that more and more of the
    Platform's integrity depends upon the integrity of Java code, which
    can be violated by the unsafe APIs.

  - _Maintainability_ — In order to add new features without drowning in
    maintenance, we need to be able to remove obsolete components from
    the JDK and refactor its implementation at will.  Unfortunately,
    over time various libraries, frameworks, and tools came to depend on
    some of the JDK's internal APIs, which they assumed were stable.  As
    a result, it was increasingly difficult for the ecosystem to migrate
    to newer releases.  We could either accept a slowing pace of
    Platform evolution or inflict migration pain just once more, in
    JDK&nbsp;17, by strongly encapsulating the JDK's internal APIs.  (We
    modularized the JDK in JDK&nbsp;9, but we only fully enabled strong
    encapsulation in JDK&nbsp;17.)

  - _Security_ — With the impending removal of the Security Manager
    ([JEP 411](https://openjdk.org/jeps/411)), we need strong
    encapsulation to support the creation of robust security layers
    protected from interference by other code, as shown earlier.
    Without strong encapsulation, any vulnerable code in the application
    could compromise security.

  - _Performance_ — There is growing demand to improve startup time,
    warmup time, and image size, which are important for deploying Java
    applications in modern cloud environments.  Some techniques for
    achieving these goals require that classes do not change over time
    by, e.g., being redefined via the Instrumentation API.  Other
    important optimizations, such as constant folding, require that
    constructs such as `final` fields have integrity, so that their
    values are actually final and cannot be modified.

In short: The use of JDK-internal APIs caused serious migration issues,
there was no practical mechanism that enabled robust security in the
current landscape, and new requirements could not be met.  Despite the
value that the unsafe APIs offer to libraries, frameworks, and tools,
the ongoing lack of integrity is untenable.  Strong encapsulation and
the restriction of the unsafe APIs — by default — are the solution.