Yes, we're now running our Black Friday Sale. All Access and Pro are 33% off until 2nd December, 2025:
JFR Event to Detect Invocations of Deprecated Methods in Java
Last updated: October 4, 2025
1. Overview
JDK 22 introduced a new JFR (Java Flight Recorder) event that detects the usage of deprecated methods within the JDK. The jdk.DeprecatedInvocation event records direct method calls originating from outside the JDK. In this article, we’ll look at how these events are captured and examine scenarios where they are—and are not—recorded.
2. Setup
To demonstrate these features, let’s use a simple Spring Boot REST resource that calls deprecated JDK APIs:
@RestController
public class DeprecatedApiDemo {
@GetMapping("/deprecated")
public String triggerDeprecated() {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
System.setProperty("demo.log", "true");
return null;
});
Boolean b = new Boolean("true");
return "Done";
}
}
The AccessController class has been deprecated since JDK 17 and marked for removal in future JDK versions, while the Boolean constructor has been deprecated since JDK 9 and also marked for removal. When we call these methods from outside the JDK, JFR should detect the operation.
Let’s also make sure we’re running JDK 22 or later. If using SDKMAN, we can switch SDKs as follows:
$ sdk use java <sdk-identifier>
3. Capturing Events
When starting our service, let’s specify the -XX:StartFlightRecording flag in the startup command so that the events are captured:
$ mvn spring-boot:run -Dspring-boot.run.jvmArguments="-XX:StartFlightRecording=filename=demo-deprecated.jfr"
The flight recording flag in this command reports only direct deprecated JDK method invocations initiated from outside the JDK, specifically those marked with @Deprecated(forRemoval=true). The demo-deprecated.jfr file is created when the service starts, but its content is flushed to the file only after the service terminates.
Next, let’s make a call to our REST endpoint:
$ curl http://localhost:8080/deprecated
Let’s stop our running service and examine the flight recording file for deprecated method invocations:
$ jfr view jdk.DeprecatedInvocation demo-deprecated.jfr
This command prints output similar to the following:
| Deprecated Method Invocation |
| Time | Stack Trace | Deprecated Method | Invocation Time | For Removal |
|----------|------------------------------------------|------------------------------------------|-----------------|-------------|
| 10:22:09 | org.apache.tomcat.util.threads.ThreadP...| java.lang.System.getSecurityManager() | 10:22:09 | true |
| 10:22:09 | com.baeldung.jfr_event_demo.Deprecated...| java.lang.Boolean.<init>(String) | 10:22:06 | true |
| 10:22:09 | com.baeldung.jfr_event_demo.Deprecated...| java.security.AccessController.doPrivi...| 10:22:06 | true |
| 10:22:09 | jakarta.security.auth.message.config.A...| java.security.AccessController.doPrivi...| 10:22:06 | true |
| 10:22:09 | jakarta.security.auth.message.config.A...| java.security.AccessController.doPrivi...| 10:22:06 | true |
| 10:22:09 | jakarta.security.auth.message.config.A...| java.lang.System.getSecurityManager() | 10:22:06 | true |
| 10:22:09 | com.baeldung.jfr_event_demo.StartupTes...| java.security.AccessController.doPrivi...| 10:21:57 | true |
| 10:22:09 | org.apache.tomcat.util.threads.Constan...| java.lang.System.getSecurityManager() | 10:21:56 | true |
| 10:22:09 | org.apache.tomcat.util.compat.JrePlatf...| java.lang.System.getSecurityManager() | 10:21:56 | true |
| 10:22:09 | org.apache.tomcat.util.threads.TaskThr...| java.lang.System.getSecurityManager() | 10:21:56 | true |
| 10:22:09 | org.apache.catalina.loader.WebappClass...| java.lang.System.getSecurityManager() | 10:21:56 | true |
| 10:22:09 | org.apache.catalina.Globals.<clinit>() | java.lang.System.getSecurityManager() | 10:21:56 | true |
| 10:22:09 | org.slf4j.LoggerFactory.getServiceLoad...| java.lang.System.getSecurityManager() | 10:21:55 | true |
From the printout, we can see that only the invocations marked for removal were reported, i.e., forRemoval is true. To report all deprecated JDK methods used by our service, including those marked @Deprecated(forRemoval=false), let’s update our start command to include this setting:
$ mvn spring-boot:run -Dspring-boot.run.jvmArguments="-XX:StartFlightRecording:jdk.DeprecatedInvocation#level=all,filename=demo-deprecated.jfr"
The all level flag indicates that all deprecated invocations should be reported. After executing the curl command and stopping the service, the demo-deprecated.jfr file should display a list similar to the following:
| Deprecated Method Invocation |
| Time | Stack Trace | Deprecated Method | Invocation Time | For Removal |
|----------|------------------------------------------|------------------------------------------|-----------------|-------------|
| 11:55:12 | org.apache.tomcat.util.threads.ThreadP... | java.lang.System.getSecurityManager() | 11:55:12 | true |
| 11:55:12 | com.fasterxml.jackson.databind.util.in... | java.lang.Thread.getId() | 11:55:08 | false |
| 11:55:12 | com.baeldung.jfr_event_demo.Deprecated... | java.math.BigDecimal.setScale(int, int) | 11:55:08 | false |
| 11:55:12 | com.baeldung.jfr_event_demo.Deprecated... | java.lang.Boolean.<init>(String) | 11:55:08 | true |
| 11:55:12 | com.baeldung.jfr_event_demo.Deprecated... | java.security.AccessController.doPrivi... | 11:55:08 | true |
| 11:55:12 | jakarta.security.auth.message.config.A... | java.security.AccessController.doPrivi... | 11:55:08 | true |
| 11:55:12 | jakarta.security.auth.message.config.A... | java.security.AccessController.doPrivi... | 11:55:08 | true |
| 11:55:12 | jakarta.security.auth.message.config.A... | java.lang.System.getSecurityManager() | 11:55:08 | true |
| 11:55:12 | org.apache.coyote.Request.setRequestTh... | java.lang.Thread.getId() | 11:55:08 | false |
| 11:55:12 | com.baeldung.jfr_event_demo.StartupTes... | java.security.AccessController.doPrivi... | 11:55:02 | true |
| 11:55:12 | org.apache.catalina.webresources.Cache... | java.net.URL.<init>(...) | 11:55:02 | false |
| 11:55:12 | org.springframework.util.ConcurrentLru... | java.lang.Thread.getId() | 11:55:02 | false |
| 11:55:12 | org.springframework.util.ReflectionUti... | java.lang.reflect.AccessibleObject.isA... | 11:55:02 | false |
| 11:55:12 | org.apache.tomcat.util.threads.Constan... | java.lang.System.getSecurityManager() | 11:55:02 | true |
| 11:55:12 | org.apache.tomcat.util.compat.JrePlatf... | java.lang.System.getSecurityManager() | 11:55:02 | true |
| 11:55:12 | org.apache.tomcat.util.threads.TaskThr... | java.lang.System.getSecurityManager() | 11:55:02 | true |
| 11:55:12 | org.apache.catalina.loader.WebappClass... | java.lang.System.getSecurityManager() | 11:55:02 | true |
| 11:55:12 | org.apache.catalina.Globals.<clinit>() | java.lang.System.getSecurityManager() | 11:55:02 | true |
| 11:55:12 | org.springframework.util.ReflectionUti... | java.lang.reflect.AccessibleObject.isA... | 11:55:01 | false |
| 11:55:12 | org.springframework.util.ResourceUtils... | java.net.URL.<init>(String) | 11:55:01 | false |
| 11:55:12 | org.springframework.util.ReflectionUti... | java.lang.reflect.AccessibleObject.isA... | 11:55:01 | false |
| 11:55:12 | org.slf4j.LoggerFactory.getServiceLoad... | java.lang.System.getSecurityManager() | 11:55:01 | true |
From this table, we can see that even methods deprecated but not yet marked for removal were reported.
4. Scenarios Where It Does Not Work
As mentioned earlier, JFR reports only deprecated JDK methods, meaning deprecated fields and methods outside the JDK are not reported. To demonstrate this, let’s deprecate a method that exists outside the JDK:
@Component
public class LegacyClass {
@Deprecated(forRemoval = true)
public void oldMethod() {
System.out.println("Deprecated method");
}
}
Next, let’s expose and call the endpoint for this method:
@RestController
public class DeprecatedApiDemo {
@Autowired
LegacyClass legacyClass;
//...
@GetMapping("/deprecated2")
public String triggerDeprecated2() {
legacyClass.oldMethod();
return "Completed";
}
}
$ curl http://localhost:8080/deprecated2
Examining the generated JFR file reveals that the oldMethod method is not recorded:
| Deprecated Method Invocation |
| Time | Stack Trace | Deprecated Method | Invocation Time | For Removal |
|----------|------------------------------------------|-------------------------------------------|-----------------|-------------|
| 13:33:55 |org.apache.coyote.Constants.<clinit>() | java.lang.System.getSecurityManager() | 13:33:50 | true |
| 13:33:55 |com.fasterxml.jackson.databind.util.in... | java.lang.Thread.getId() | 13:33:50 | false |
| 13:33:55 |jakarta.security.auth.message.config.A... | java.security.AccessController.doPrivi... | 13:33:50 | true |
| 13:33:55 |jakarta.security.auth.message.config.A... | java.security.AccessController.doPrivi... | 13:33:50 | true |
| 13:33:55 |jakarta.security.auth.message.config.A... | java.lang.System.getSecurityManager() | 13:33:50 | true |
| 13:33:55 |org.apache.coyote.Request.setRequestTh... | java.lang.Thread.getId() | 13:33:50 | false |
| 13:33:55 |com.baeldung.jfr_event_demo.StartupTes... | java.security.AccessController.doPrivi... | 13:33:40 | true |
| 13:33:55 |org.apache.catalina.webresources.Cache... | java.net.URL.<init>(...) | 13:33:39 | false |
| 13:33:55 |org.springframework.util.ConcurrentLru... | java.lang.Thread.getId() | 13:33:39 | false |
| 13:33:55 |org.springframework.util.ReflectionUti... | java.lang.reflect.AccessibleObject.isA... | 13:33:39 | false |
| 13:33:55 |org.apache.tomcat.util.threads.Constan... | java.lang.System.getSecurityManager() | 13:33:39 | true |
| 13:33:55 |org.apache.tomcat.util.compat.JrePlatf... | java.lang.System.getSecurityManager() | 13:33:39 | true |
| 13:33:55 |org.apache.tomcat.util.threads.TaskThr... | java.lang.System.getSecurityManager() | 13:33:39 | true |
| 13:33:55 |org.apache.catalina.loader.WebappClass... | java.lang.System.getSecurityManager() | 13:33:39 | true |
| 13:33:55 |org.apache.catalina.Globals.<clinit>() | java.lang.System.getSecurityManager() | 13:33:39 | true |
| 13:33:55 |org.springframework.util.ReflectionUti... | java.lang.reflect.AccessibleObject.isA... | 13:33:39 | false |
| 13:33:55 |org.springframework.util.ResourceUtils... | java.net.URL.<init>(String) | 13:33:38 | false |
| 13:33:55 |org.springframework.util.ReflectionUti... | java.lang.reflect.AccessibleObject.isA... | 13:33:38 | false |
| 13:33:55 |org.slf4j.LoggerFactory.getServiceLoad... | java.lang.System.getSecurityManager() | 13:33:38 | true |
5. Special Considerations
A limitation of the generated event is that, unless an invocation is JIT-compiled, only one call site is reported. Let’s demonstrate this by calling the same deprecated method from the same class:
@Component
public class LegacyClass {
//...
public void callDeprecatedMethod() {
Boolean boolean1 = new Boolean("true");
}
public void wrapperCall() {
callDeprecatedMethod();
Boolean boolean2 = new Boolean("false");
}
}
Let’s expose and call this method:
@RestController
public class DeprecatedApiDemo {
@Autowired
LegacyClass legacyClass;
//...
@GetMapping("/deprecated3")
public String triggerDeprecated3() {
legacyClass.wrapperCall();
return "Finished";
}
}
$ curl http://localhost:8080/deprecated3
The generated JFR file shows just one call site:
| Deprecated Method Invocation |
| Time | Stack Trace | Deprecated Method | Invocation Time | For Removal |
|--------- |------------------------------------------|------------------------------------------|-----------------|-------------|
| 13:55:06 | org.apache.tomcat.util.threads.ThreadP...| java.lang.System.getSecurityManager() | 13:55:06 | true |
| 13:55:06 | com.fasterxml.jackson.databind.util.in...| java.lang.Thread.getId() | 13:54:59 | false |
| 13:55:06 | com.baeldung.jfr_event_demo.LegacyClas...| java.lang.Boolean.<init>(String) | 13:54:59 | true |
| 13:55:06 | jakarta.security.auth.message.config.A...| java.security.AccessController.doPrivi...| 13:54:59 | true |
Let’s adjust the call to manually trigger a JIT compilation:
@Component
public class LegacyClass {
//...
public void wrapperCall() {
for (int i = 0; i < 26000; i++) {
callDeprecatedMethod();
}
callDeprecatedMethod();
Boolean boolean2 = new Boolean("false");
}
}
After restarting the service and re-running the request, we now see two call sites for the deprecated method invocations:
| Deprecated Method Invocation |
| Time | Stack Trace | Deprecated Method | Invocation Time | For Removal |
|----------|------------------------------------------|------------------------------------------|-----------------|-------------|
| 14:15:53 | org.apache.tomcat.util.threads.ThreadP...| java.lang.System.getSecurityManager() | 14:15:53 | true |
| 14:15:53 | org.apache.coyote.Constants.<clinit>() | java.lang.System.getSecurityManager() | 14:15:42 | true |
| 14:15:53 | com.fasterxml.jackson.databind.util.in...| java.lang.Thread.getId() | 14:15:42 | false |
| 14:15:53 | com.baeldung.jfr_event_demo.LegacyClas...| java.lang.Boolean.<init>(String) | 14:15:42 | true |
| 14:15:53 | com.baeldung.jfr_event_demo.LegacyClas...| java.lang.Boolean.<init>(String) | 14:15:42 | true |
| 14:15:53 | jakarta.security.auth.message.config.A...| java.security.AccessController.doPrivi...| 14:15:42 | true |
6. Conclusion
In this article, we examined the new JFR event that detects invocations of deprecated JDK methods. We explored how to configure a service at start-up to report these instances, and finally, we discussed scenarios where such events are not captured.
The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
















