Blocks :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
Summary ------ The Security Manager has not been the primary means of securing client-side Java code for many years, it has rarely been used to secure server-side code, and it is costly to maintain. We therefore deprecated it for removal in Java 17 via [JEP 411] (2021). As the next step toward removing the Security Manager, we will revise the Java Platform specification so that developers cannot enable it and other Platform classes do not refer to it. This change will have no impact on the vast majority of applications, libraries, and tools. We will remove the Security Manager API in a future release. [JEP 411]: https://openjdk.org/jeps/411 Goals ----- - Remove the ability to enable the Security Manager when starting the Java runtime (`java -Djava.security.manager ...`). - Remove the ability to install a Security Manager while an application is running (`System.setSecurityManager(...)`). - Improve the maintainability of hundreds of JDK classes that currently delegate resource-access decisions to the Security Manager. - Revise the specification of the Security Manager API so that all implementations of it behave as if no Security Manager is ever enabled. - Retain the Security Manager API in this release, so that maintainers of existing code that depends upon it have time to migrate away. Non-Goals --------- - It is not a goal to provide a replacement for any of the Security Manager's functionality, in particular the ability to sandbox Java code or intercept calls to the Java Platform API. Motivation ---------- The Security Manager has been a feature of the Java Platform since its first release. It is based upon the *principle of least privilege:* Code is untrusted by default, so it cannot access resources such as the filesystem or the network, and developers place trust in specific code by granting it permission to access specific resources. In theory, this can protect machines and applications against code that contains accidental vulnerabilities or was crafted with malicious intent. In practice, however, the permission scheme is so complex that the Security Manager has always been disabled by default, and its use is exceedingly rare. Despite the fact that the Security Manager is disabled by default, the least-privilege model induces extraordinary complexity in the Java Platform libraries. From networking, I/O, and JDBC, to XML, AWT, and Swing, the libraries must implement the least-privilege model in case the Security Manager is enabled: - Over 1,000 methods must check for permission to access resources when the Security Manager is enabled. For example, the [constructors](https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/io/FileOutputStream.html#%3Cinit%3E(java.lang.String)) of the `FileOutputStream` class [delegate to the Security Manager](https://github.com/openjdk/jdk/blob/ae63aaaa5847a68542e1483ecf1f0d5a3704e741/src/java.base/share/classes/java/io/FileOutputStream.java#L236-L239), which applies a complex algorithm to determine whether access is permitted. - Over 1,200 methods must elevate their privileges when the Security Manager is enabled. For example, if an application does not have permission to read files, but it invokes [`java.time.LocalDateTime.now()`](https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/time/LocalDateTime.html#now()), then the `java.time` code must [assert its own, stronger permission](https://github.com/openjdk/jdk/blob/ae63aaaa5847a68542e1483ecf1f0d5a3704e741/src/java.base/share/classes/java/time/zone/ZoneRulesProvider.java#L151) in order to read the JDK’s internal time-zone database file. The [OpenJDK Core Libraries Group](https://openjdk.org/census#core-libs) devotes significant time and energy to reviewing every change to any of these methods. Every new API must be designed, and its implementation carefully audited, with the least-privilege model in mind. However, only a tiny number of applications actually enable the Security Manager. To make matters worse, most of them, in our experience, blindly grant all permissions to their code, thereby giving up the benefits of the least-privilege model. We therefore deprecated the Security Manager, for removal, in Java 17 via [JEP 411] (2021). In addition to terminally deprecating the Security Manager API and related APIs, we also revised the JDK to issue warning messages when the Security Manager is enabled. These changes were designed to prepare users and developers for the removal of the Security Manager in a future release. ### Deprecating the Security Manager had hardly any impact JDK 17 and later releases enjoyed wide adoption as developers and enterprises upgraded from JDK 8 and JDK 11. We have seen hardly any discussion in the Java ecosystem about the [warnings] that these releases issue when enabling the Security Manager. This indicates that the Security Manager is almost completely irrelevant to current Java developers. We appear to have been correct when we said, in [JEP 411], that, > "In the quarter-century since the Security Manager was introduced, adoption has been low”, and, > "In summary, there is no significant interest in developing modern Java applications with the Security Manager." Since the release of JDK 17, the maintainers of some of the handful of frameworks and tools that supported the Security Manager have removed support for it; these include [Derby](https://lists.apache.org/thread/c9qvfzvfh5lc9n41rb0638rwx2vdjz80), [Ant](https://ant.apache.org/antnews.html#Apache%20Ant%201.10.14), [SpotBugs](https://github.com/spotbugs/spotbugs/issues/1579), and [Tomcat](https://tomcat.apache.org/tomcat-11.0-doc/security-howto.html#Security_manager). The maintainers of Jakarta EE [removed the requirement for EE applications to support the Security Manager](https://jakarta.ee/specifications/platform/11/jakarta-platform-spec-11.0-m4#jakarta-ee-security-manager-related-requirements). We are not aware of any new projects that support the Security Manager. [warnings]: https://openjdk.org/jeps/411#Issue-warnings ### Moving forward The vast majority of applications, libraries, and tools do not require the Security Manager, do not recommend the Security Manager, do not use the Security Manager, and do not work if other code uses the Security Manager. It is time for the Java ecosystem to take the next step and stop using the Security Manager entirely. We will therefore revise the specification of the Security Manager so that developers cannot enable it, and we will revise the specifications of other Java Platform libraries so that they do not delegate resource-access decisions to it. We will leave a minimal version of the `java.lang.SecurityManager` class in place for compatibility with the few applications, libraries, and tools that still use it. We will remove this class in a future release. ### Removing the Security Manager will improve Java security We believe that the vast majority of Java developers would prefer to see the OpenJDK Core Libraries Group focus on practical security features needed by Internet-facing applications. The above revisions to the specification will allow us to remove the implementation of the Security Manager from the JDK codebase, together with thousands of permission checks and privilege elevations. That, in turn, will make more contributor time and energy available for other work such as - Implementing new protocols such as TLS 1.3 and [HTTP/3](https://openjdk.org/jeps/8291976), - Implementing modern, stronger cryptographic algorithms such as HSS/LMS, SHA-3, RSASSA-PSS, and EdDSA, - [Deprecating and disabling weak cryptographic protocols and algorithms](https://www.java.com/en/jre-jdk-cryptoroadmap.html), and - Introducing cryptographic APIs for [key encapsulation](https://openjdk.org/jeps/452) and [key derivation](https://openjdk.org/jeps/478), which will provide foundational support for post-quantum cryptographic algorithms. Most contemporary security threats involve malicious data, against which the Security Manager is ill equipped to defend. Removing the implementation of the Security Manager will make more contributor time and energy available for security features that defend against malicious data directly, such as: - _Safer serialization_ — The process of deserialization involves interpreting a stream of data that might have been crafted with malicious intent. In 2017, Java 9 introduced [deserialization filters](https://docs.oracle.com/en/java/javase/23/core/serialization-filtering1.html) so that developers can prevent malicious data from being deserialized in the first place. For the longer term, work is already underway [toward a better approach to serialization](https://openjdk.org/projects/amber/design-notes/towards-better-serialization). - _Stricter XML processing_ — XML documents can refer to Document Type Definitions (DTDs) anywhere on the Internet, causing the JDK to open network connections to untrusted machines. In JDK 23, application developers can lock down XML processing by starting the Java runtime with `java -Djava.xml.config.file=...` and specifying a [configuration file](https://bugs.openjdk.org/browse/JDK-8331016) that prohibits outbound connections. ### Sandboxing Java code _Sandboxing_ is the ability to run some Java code with different permissions than other code. The permissions for each piece of code are determined by a security policy, which is enforced by the Security Manager. Code running with restricted permissions, such as untrusted or potentially hostile code, is often referred to as being _in a sandbox_. Historically, the Security Manager was used to sandbox applets; we never recommended it for sandboxing entire applications. Java applications should be sandboxed in the same way as native applications, using technologies outside the JDK such as [containers](https://en.wikipedia.org/wiki/Containerization_(computing)), [hypervisors](https://en.wikipedia.org/wiki/Hypervisor), and OS mechanisms such as the [macOS App Sandbox](https://developer.apple.com/documentation/security/app_sandbox) or the [Linux seccomp feature](https://en.wikipedia.org/wiki/Seccomp). Like the Security Manager, these technologies can restrict how applications use local and remote resources; they can, e.g., prevent code from accessing the network to exfiltrate data. Unlike the Security Manager, however, they have wide adoption and are relatively easy to learn and use effectively. ### Intercepting calls to the Java Platform API A small number of applications have used the Security Manager not to enforce a security policy but, rather, as a means to intercept calls to the Java Platform API. In the absence of a security policy and permission checking, calls to the Java Platform API are no longer a security concern as such. Truly malicious code has innumerable ways to bypass the Security Manager's interception of API calls. Nonetheless, some applications find interception useful, particularly to block calls to methods such as `System::exit`. We designed, prototyped, and evaluated a variety of mechanisms that an application could use instead of the Security Manager to intercept calls to the Java Platform API. We found that the use cases were too broad, and the requirements too diffuse, to support the introduction of such a mechanism. The OpenJDK Core Libraries Group is unwilling to maintain an arbitrary number of API interception points with ill-defined requirements across the JDK in perpetuity. In most cases, we found that issues that seemed to need interception could be adequately addressed outside the JDK, using techniques such as source code modification, static code analysis and rewriting, or [agent-based](https://docs.oracle.com/en/java/javase/23/docs/api/java.instrument/java/lang/instrument/package-summary.html) dynamic code rewriting at class load time. See the [Appendix](#Appendix) for an example of an agent that uses dynamic code rewriting to intercept calls to `System::exit`. ### The Security Manager in older Java releases The Security Manager will continue to be available in every release prior to JDK 24. Application deployers who are wary of adopting new releases because they value stability above all else are unlikely ever to upgrade to JDK 24, and therefore will never be affected by changes to the Security Manager in JDK 24 or subsequent releases. Description ----------- In JDK 24, we will: - Remove the ability to enable the Security Manager at startup, - Remove the ability to install a custom Security Manager during run time, and - Render the Security Manager API non-functional, in advance of removing the API in a future release. ### Enabling the Security Manager in JDK 24 is an error In JDK 24, you cannot enable the Security Manager at startup, nor can you install a custom Security Manager during run time. - It is an error to enable a Security Manager at startup, for example via: ``` $ java -Djava.security.manager -jar app.jar $ java -Djava.security.manager="" -jar app.jar $ java -Djava.security.manager=allow -jar app.jar $ java -Djava.security.manager=default -jar app.jar $ java -Djava.security.manager=com.foo.CustomSM -jar app.jar ``` Attempting to do so causes the JVM to report the error and then exit: ``` Error occurred during initialization of VM java.lang.Error: A command line option has attempted to allow or enable the Security Manager. Enabling a Security Manager is not supported. at java.lang.System.initPhase3(java.base@24/System.java:2067) ``` You cannot suppress this error message, nor can you reduce it to the [warnings given in JDK 17 through 23][warnings]. (The five invocations of java -D... shown above set the system property `java.security.manager` to, respectively, the empty string, the empty string, the string `allow`, the string `default`, and the class name of a custom Security Manager.) - It is not an error to disable the installation of a custom Security Manager during run time, for example via: ``` $ java -jar app.jar $ java -Djava.security.manager=disallow -jar app.jar ``` No warning or error message is issued at startup, and the application runs without a Security Manager, just as it did before. (The default value of `java.security.manager` [has been `disallow` since JDK 18](https://bugs.openjdk.java.net/browse/JDK-8270380), so `java -jar app.jar` means the same as `java -Djava.security.manager=disallow -jar app.jar`.) - It is an error to install a Security Manager at run time by calling `System::setSecurityManager`. Attempting to do so causes the JVM to throw an `UnsupportedOperationException` with the detail message > `Setting a Security Manager is not supported` ### How to determine if an application enables the Security Manager If you are not sure whether your application enables the Security Manager, here are some things you can do to find out: - Check scripts or documentation to see if the application is launched with the Security Manager allowed or enabled, via command line options, or requires policy files to be installed and configured. - Run the application on one of JDK 17 through 23 and look for [messages on the console warning that the Security Manager is deprecated and will be removed in a future release][warnings]. - Run the application on one of JDK 17 through 23 with the command-line option `-Djava.security.manager=disallow`. If the application installs a custom Security Manager via the `System::setSecurityManager` method then the JVM will throw an `UnsupportedOperationException`. - Use the [`jdeprscan`](https://docs.oracle.com/en/java/javase/23/docs/specs/man/jdeprscan.html) tool in JDK 17 through 23 to scan for uses of deprecated Security Manager APIs such as `System::setSecurityManager` or `java.security.Policy::setPolicy`. ### Rendering the Security Manager API non-functional The Security Manager API consists of: - Methods in the `java.lang.SecurityManager` class, - Methods in the `AccessController`, `AccessControlContext`, `Policy`, and `ProtectionDomain` classes of the `java.security` package, and - The `getSecurityManager` and `setSecurityManager` methods in the `java.lang.System` class. We are not removing these methods from Java 24; rather, we are changing them to have no effect. They will, as appropriate, return `null` or `false`, or pass through the caller's request, or unconditionally throw a `SecurityException`. The full set of behavioral changes is available [here](https://bugs.openjdk.org/browse/JDK-8338412). In addition to changing the API itself, we will: - Remove the [system policy file](https://docs.oracle.com/en/java/javase/23/security/permissions-jdk1.html#GUID-789089CA-8557-4017-B8B0-6899AD3BA18D), `conf/security/java.policy`. - Cause system properties specific to the Security Manager, in particular [`java.security.policy`](https://docs.oracle.com/en/java/javase/23/security/permissions-jdk1.html#GUID-75C71299-8B56-4AC9-A83F-41BC14535545) and [`jdk.security.filePermCompat`](https://docs.oracle.com/en/java/javase/23/security/permissions-jdk1.html#GUID-83063225-0ACB-4909-9BAB-7F7D4E3749E2__LISTOFSYSTEMPROPERTIESTOCUSTOMIZEPA-C3268ADF), to be ignored. We will later document the complete list of affected system properties. - Cause security properties specific to the Security Manager, in particular [`policy.provider`](https://docs.oracle.com/en/java/javase/23/security/permissions-jdk1.html#GUID-75C71299-8B56-4AC9-A83F-41BC14535545), [`package.access`](https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/SecurityManager.html#checkPackageAccess(java.lang.String)), and [`package.definition`](https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/SecurityManager.html#checkPackageDefinition(java.lang.String)), to be ignored. We will later document the complete list of affected security properties. - Cause the `access` and `policy` options of the `java.security.debug` system property to be ignored, since they no longer apply. See [Troubleshooting Security](https://docs.oracle.com/en/java/javase/23/security/troubleshooting-security.html) in the _Security Developer's Guide_ for information about how to use this system property. ### Changes elsewhere in the Java Platform API Approximately 1,000 constructors and methods in the Platform are specified to throw `SecurityException` if the Security Manager is enabled and appropriate permissions have not been granted. They span 264 classes, 73 packages, and 25 modules. For example, `java.base` has 640 methods specified to throw `SecurityException`. In Java 24, we will revise the specifications of all such constructors and methods to remove mentions of `SecurityException` since that exception will now never be thrown. The complete list of revised constructors and methods is available [here](https://bugs.openjdk.org/browse/JDK-8338412). Here is an example of the specification change for a [constructor](https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/io/FileOutputStream.html#%3Cinit%3E(java.lang.String)) in `java.io.FileOutputStream` (struck through text is deleted): <pre> public FileOutputStream(String name) throws FileNotFoundException Creates a file output stream to write to the file with the specified name. A new FileDescriptor object is created to represent this file connection. <del>First, if there is a security manager, its checkWrite method is called with name as its argument.</del> If the file exists but is a directory rather than a regular file, does not exist but cannot be created, or cannot be opened for any other reason then a FileNotFoundException is thrown. <b>Implementation Requirements:</b> Invoking this constructor with the parameter name is equivalent to invoking new FileOutputStream(name, false). <b>Parameters:</b> name - the system-dependent file name. <b>Throws:</b> FileNotFoundException - if the file exists but is a directory rather than a regular file, does not exist but cannot be created, or cannot be opened for any other reason <del>SecurityException - if a security manager exists and its checkWrite method denies write access to the file.</del> <del><b>See Also:</b> SecurityManager.checkWrite(java.lang.String)</del> </pre> ### Advice to maintainers of libraries that support the Security Manager A small number of libraries were designed to use the Security Manager if it is enabled. These libraries typically employ two idioms: - Calling `System::getSecurityManager` to check if a Security Manager is enabled and, if so, calling `SecurityManager::checkPermission` to check if an operation should be granted or denied: ``` SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(...); } ``` - Calling `AccessController::doPrivileged` to execute code with different permissions from the calling code: ``` SomeReturnValue v = AccessController.doPrivileged(() -> { ... return theResult; }); ``` <a id="check-methods"/> In JDK 24, where a Security Manager is never enabled, the `System::getSecurityManager` and `AccessController::doPrivileged` methods behave as they did in JDK 17 when a Security Manager was not enabled: - `System::getSecurityManager` returns `null`, - The `SecurityManager::check*` methods throw `SecurityException`, and - The six `AccessController::doPrivileged` methods execute the given action immediately. Accordingly, the small number of libraries that call these methods will run on JDK 24 without change. However, we strongly recommend that new releases of these libraries do not call these methods, which we will remove in a future release. A very small number of libraries use advanced parts of the Security Manager API to implement a custom execution environment. For example, a library might call `AccessController::checkPermission` to enforce its own permission model, or call `Policy::setPolicy` to make custom Security Managers treat certain resources as off-limits. These methods are non-functional in JDK 24 in order to provide an execution environment that disallows access to all resources by default. We will remove them in a future JDK release. Future Work ----------- ### Removing related APIs We are not removing any classes or methods from the Java Platform API in Java 24. In a future release we will remove the Security Manager APIs that we deprecated in Java 17. In future releases we may, further, deprecate and remove additional classes and methods in the `java.lang` and `java.security` packages. - We are not deprecating `SecurityException` now because it is used elsewhere in the JDK in situations that are unrelated to the Security Manager, despite the fact that its specification says, "Thrown by the security manager to indicate a security violation". Here are examples of its (mis)use: - `java.lang.ClassLoader::defineClass` throws `SecurityException` if the name of the class being defined starts with "`java.`". - `java.lang.reflect.Constructor::setAccessible` throws it if invoked on the `Constructor` object for a constructor of `java.lang.Class`. - `java.util.java.JarInputStream` throws it when signed JAR entries are signed incorrectly. After reexamining these misuses, we may deprecate `SecurityException` in a future release. - In a future release we will deprecate `Permission` and related classes such as `BasicPermission`, `PermissionCollection`, and `Permissions`, and also subclasses of `Permission` outside of the `java.security` package, such as `java.lang.RuntimePermission`, `java.net.NetPermission`, and `java.lang.reflect.ReflectPermission`. - In a future release we will deprecate `PrivilegedAction`, `PrivilegedExceptionAction`, and `PrivilegedActionException`. We did not deprecate these classes in Java 17 because they appear in method signatures of `javax.security.auth.Subject`, a class unrelated to the Security Manager. In Java 18, we added replacement methods to `javax.security.auth.Subject` that do not use the `Privileged*` classes. We will eventually remove both the old methods and the `Privileged*` classes. ### Removing or revising related features Various early Java Platform features were designed around a vision of mobile objects. They used serialization to move code and data between JVMs, and assumed applications would enable the Security Manager to defend against maliciously serialized objects. This vision did not gain any traction. Given the [fundamental flaws in serialization](https://openjdk.org/projects/amber/design-notes/towards-better-serialization) and the minimal use of the Security Manager, we have either already removed these features or else we plan to do so: - JMX Management Applets ("m-lets") were introduced in Java 5 and allowed remote MBeans to be dynamically loaded and executed with the Security Manager enabled. M-lets saw essentially no use. We [deprecated the m-let API for removal in Java 20](https://bugs.openjdk.org/browse/JDK-8297794) and [removed it in Java 23](https://bugs.openjdk.org/browse/JDK-8318707). - JNDI has support for reconstructing objects that were serialized to an LDAP database ([RFC 2713](https://www.ietf.org/rfc/rfc2713.txt)). This feature of JNDI has been disabled by default since [Java 6](https://www.oracle.com/java/technologies/javase/6u13.html) but can be enabled via system properties. Using this feature securely relies on enabling the Security Manager, so it will not be possible to use the feature securely once the Security Manager is removed. Accordingly, we will [remove this feature](https://bugs.openjdk.org/browse/JDK-8338536), along with the remote class loading feature of the JNDI RMI Registry service provider, in a future release. - RMI supports [dynamic code loading](https://docs.oracle.com/en/java/javase/23/docs/specs/rmi/arch.html#dynamic-class-loading), but it is enabled only when the Security Manager is enabled. This feature of RMI has been disabled by default since 2013. With the removal of the Security Manager, it is no longer possible to use this feature. We may remove it in a future release. Separately, the `javax.xml` APIs allow Java source code to be embedded directly in XSLT and XPath documents as extension functions. This feature is [enabled by default](https://docs.oracle.com/en/java/javase/23/docs/api/java.xml/module-summary.html#jdk.xml.enableExtensionFunctions) but, historically, it was disabled when running with the Security Manager. We will disable this feature by default in a future release, as part of a broader effort toward stricter XML processing. Testing ------- The breadth of the Security Manager API and the depth of its support in the JDK codebase is reflected in the approximately 4,000 tests developed for it since JDK 1.0. They fall into three categories: - Tests that directly exercise functionality of the Security Manager, e.g., making sure that permissions are enforced correctly. - Tests that address security vulnerabilities, which typically ensure that it is no longer possible for specific exploits that allowed untrusted code, such as applets, to escape the sandbox. - Conformance tests which ensure that the Security Manager implementation complies with the specification of the Security Manager API. Permanently disabling the Security Manager will make these tests irrelevant since the functionality will no longer be supported and the concept of a sandbox will no longer exist. Including tests, we will delete over 50,000 lines of code. Alternatives ------------ The numerous `check*` methods in the Security Manager API always [throw an exception](#check-methods) so as to avoid unconditionally permitting operations that formerly required a permission check, and thus might not have been permitted. This may be inconvenient for application maintainers, who may have to take some corrective action. An alternative would be to have these methods always succeed, but that would allow an application to operate insecurely without notifying the maintainer. Risks and Assumptions --------------------- - In JDK 24, attempting to enable the Security Manager on the command line will immediately result in an error message and the application will not start. If an application does not start then downstream systems might fail and business processes might be impacted. We assume that application maintainers can respond to the error by updating their `java` command lines to avoid giving the `-Djava.security.manager` option and by mitigating security concerns using other mechanisms. (When we remove a feature from the JDK, we conventionally reject any associated command line options. This includes the use of `java -D...` to set a system property such as `java.security.manager`. For example, setting the [`java.ext.dirs` system property caused an error](https://docs.oracle.com/javase/9/migrate/toc.htm#JSMIG-GUID-2C896CA8-927C-4381-A737-B1D81D964B7B) when the Extension Mechanism was removed in JDK 9. This forces application maintainers to swiftly remove outdated options, avoiding situations where the JDK is run with a confusing or misleading set of options.) - There is a risk that frameworks which rely on the `javax.security.auth` API still use the deprecated methods in the `Subject` class, namely [`doAs`](https://docs.oracle.com/en/java/javase/23/docs/api/java.base/javax/security/auth/Subject.html#doAs(javax.security.auth.Subject,java.security.PrivilegedAction)) and [`getSubject`](https://docs.oracle.com/en/java/javase/23/docs/api/java.base/javax/security/auth/Subject.html#getSubject(java.security.AccessControlContext)). We deprecated these methods in Java 17 and 18 because their signatures use deprecated classes of the Security Manager API. We introduced replacements for `doAs` and `getSubject` in [Java 18](https://bugs.openjdk.org/browse/JDK-8267108). Since `getSubject` has [thrown an `UnsupportedOperationException` since Java 23](https://bugs.openjdk.org/browse/JDK-8328643), we assume that frameworks have become aware of the deprecation and are working to adopt the replacements, e.g., [HADOOP-19212](https://issues.apache.org/jira/browse/HADOOP-19212). Appendix -------- An [agent](https://docs.oracle.com/en/java/javase/23/docs/api/java.instrument/java/lang/instrument/package-summary.html) is a Java program that can alter the code of an application while the application is running. Agents achieve this by transforming the bytecodes of methods when classes are loaded, or by redefining classes after they have been loaded. Here is an agent that blocks code from calling `System::exit`. The agent declares a `premain` method that is run by the JVM before the `main` method of the application. This method registers a _transformer_ that transforms class files as they are loaded from the class path or module path. The transformer rewrites every call to `System.exit(int)` into `throw new RuntimeException("System.exit not allowed")`. The transformer reads and writes bytecodes in class files using the [Class-File API](https://openjdk.org/jeps/466), which is a preview feature in JDK 23. See the [`java.lang.classfile` package](https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/classfile/package-summary.html) for details. The agent's source code imports the Class-File API and other Java APIs with [module import declarations](https://openjdk.org/jeps/476), which are also a preview feature in JDK 23. ``` import module java.base; import module java.instrument; public class BlockSystemExitAgent { /* * Before the application starts, register a transformer of class files. */ public static void premain(String agentArgs, Instrumentation inst) { var transformer = new ClassFileTransformer() { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classBytes) { if (loader != null && loader != ClassLoader.getPlatformClassLoader()) { return blockSystemExit(classBytes); } else { return null; } } }; inst.addTransformer(transformer, true); } /* * Rewrite every invokestatic of System::exit(int) to an athrow of RuntimeException. */ private static byte[] blockSystemExit(byte[] classBytes) { var modified = new AtomicBoolean(); ClassFile cf = ClassFile.of(ClassFile.DebugElementsOption.DROP_DEBUG); ClassModel classModel = cf.parse(classBytes); Predicate<MethodModel> invokesSystemExit = methodModel -> methodModel.code() .map(codeModel -> codeModel.elementStream() .anyMatch(BlockSystemExitAgent::isInvocationOfSystemExit)) .orElse(false); CodeTransform rewriteSystemExit = (codeBuilder, codeElement) -> { if (isInvocationOfSystemExit(codeElement)) { var runtimeException = ClassDesc.of("java.lang.RuntimeException"); codeBuilder.new_(runtimeException) .dup() .ldc("System.exit not allowed") .invokespecial(runtimeException, "<init>", MethodTypeDesc.ofDescriptor("(Ljava/lang/String;)V"), false) .athrow(); modified.set(true); } else { codeBuilder.with(codeElement); } }; ClassTransform ct = ClassTransform.transformingMethodBodies(invokesSystemExit, rewriteSystemExit); byte[] newClassBytes = cf.transform(classModel, ct); if (modified.get()) { return newClassBytes; } else { return null; } } private static boolean isInvocationOfSystemExit(CodeElement codeElement) { return codeElement instanceof InvokeInstruction i && i.opcode() == Opcode.INVOKESTATIC && "java/lang/System".equals(i.owner().asInternalName()) && "exit".equals(i.name().stringValue()) && "(I)V".equals(i.type().stringValue()); } } ``` You must package the agent in a JAR file and specify it via the `-javaagent` option when starting the application: # Compile the agent into the agentclasses directory, enabling preview features for JDK 23 $ javac --enable-preview --release 23 -d agentclasses BlockSystemExitAgent.java # Create JAR file manifest in agent.mf $ cat > agent.mf << EOF Premain-Class: BlockSystemExitAgent Can-Retransform-Classes: true EOF # Create the agent JAR (Note there is a period after -C agentclasses) $ jar --create --file=BlockSystemExitAgent.jar --manifest=agent.mf -C agentclasses . # Run application with the agent JAR, enabling preview features for JDK 23 $ java --enable-preview -javaagent:BlockSystemExitAgent.jar -jar app.jar