Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
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 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 17, by strongly encapsulating the JDK's internal APIs. (We modularized the JDK in JDK 9, but we only fully enabled strong encapsulation in JDK 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.