Core Java

Detecting Deprecated Method Calls in Java Using JFR

Java Flight Recorder (JFR) is a powerful profiling and diagnostics tool built into the JVM that allows developers to capture detailed runtime information with minimal performance overhead. Among its many capabilities, JFR can detect deprecated method invocations via the jdk.OldDeprecatedMethod event, enabling developers to monitor usage of deprecated APIs dynamically. Let us delve into understanding how Java JFR can detect deprecated methods effectively.

1. Introduction to Java Flight Recorder (JFR)

Java Flight Recorder (JFR) is a powerful profiling and diagnostics tool built into the Java Virtual Machine (JVM). It enables low-overhead, continuous recording of JVM events during application runtime, providing valuable insights into performance, resource usage, and behavior. One of its lesser-known but highly useful capabilities is detecting invocations of deprecated methods, which helps developers identify legacy API usage without intrusive logging or manual code changes.

1.1 Setup

To use JFR to detect deprecated method invocations, you need to run your Java application with JFR enabled and configure it to record the jdk.OldDeprecatedMethod event. Here are the basic steps:

  • Use JDK 11 or newer, as JFR is bundled and well-supported from JDK 11 onwards.
  • Enable JFR recording with the appropriate event settings.
  • Configure JFR to capture deprecated method invocation events.

Example JVM options to start recording immediately with the deprecated method event enabled:

java -XX:StartFlightRecording=name=DeprecatedMethodRecording,settings=profile,dumponexit=true,filename=deprecated.jfr -XX:FlightRecorderOptions:olddeprecatedmethod=true -jar yourapp.jar

The olddeprecatedmethod=true option enables recording of the deprecated method invocation event. The recording file deprecated.jfr will contain all relevant data after the app exits.

1.2 Recording Events

The jdk.OldDeprecatedMethod event is automatically generated by the JVM whenever a deprecated method is invoked. This event contains details such as:

  • Class and method name of the deprecated method invoked.
  • Caller stack trace at the point of invocation.
  • Timestamp of the invocation.

You can analyze the resulting JFR recording using tools like Java Mission Control (JMC) or programmatically via the JFR API.

1.3 Cases Where It Fails

Despite its usefulness, there are scenarios where detecting deprecated method invocations via JFR might not work or produce incomplete results:

  • Must be enabled at JVM start: on many JVM builds, the event is only emitted when the JVM was launched with a recording requested at startup (e.g. -XX:StartFlightRecording); starting a recording later via jcmd or the API may not surface these events. This is intentional to avoid constant overhead when JFR is otherwise unused.
  • Primarily for JDK APIs: the event was added to help detect use of deprecated JDK APIs. Detection of every application-level @Deprecated call is not the primary target — coverage and behavior may differ across JDK builds. (Use custom instrumentation or application-level JFR events if you need to track app@Deprecated calls reliably.)
  • Optimizations and inlining: highly optimized or inlined calls may not appear as expected in the event stream or stack trace; JIT and aggressive optimizations can obscure frames.
  • Reflection / dynamic proxies / native calls: calls invoked through reflection, invoke dynamic, generated proxies, or native bridges may not be captured as a straightforward method invocation event.
  • Platform/vendor differences: OpenJDK builds, and vendor distributions may differ in which event names are available and how they behave; always test on the target JVM you run in production.

1.4 Key Points to Keep in Mind

When using JFR for deprecated method detection, consider the following:

  • Performance: JFR is built to be low-overhead, but enabling fine-grained events and extra diagnostics (stack traces for many events) increases overhead. Use targeted recording configurations and sampling policies in production.
  • Filtering & post-processing: you can limit noise by filtering events in the RecordingStream, by package/class name, or by post-processing the generated .jfr file with Java Mission Control (JMC) or jfr tooling.
  • Automation: integrate JFR-based checks into CI pipelines (run tests under a JVM started with -XX:StartFlightRecording and fail if jdk.DeprecatedInvocation events exist for sensitive packages). This is a pragmatic way to prevent shipping dependencies on soon-to-be-removed JDK APIs.
  • JFR tooling: use jfr print, JMC, or recorded-event parsing via the JFR API to analyze recordings. Example: bin/jfr print deprecated.jfr will show recorded jdk.DeprecatedInvocation entries.

2. Code Example

The example below demonstrates a simple Java program invoking a deprecated method. We run this program with JFR enabled to capture deprecated method invocations.

// DeprecatedExample.java
public class DeprecatedExample {

  // Deprecated method
  @Deprecated
  public void oldMethod() {
    System.out.println("Deprecated method called");
  }

  // New method replacing the deprecated one
  public void newMethod() {
    System.out.println("New method called");
  }

  public static void main(String[] args) throws InterruptedException {
    DeprecatedExample example = new DeprecatedExample();

    System.out.println("Calling new method...");
    example.newMethod();

    System.out.println("Calling deprecated method...");
    example.oldMethod();

    // Sleep to allow JFR to capture events if recording live
    Thread.sleep(2000);
  }
}

2.1 Code Explanation

This Java program defines a class DeprecatedExample with two methods: oldMethod(), marked as deprecated using the @Deprecated annotation, and newMethod(), which serves as its replacement. The main method creates an instance of this class, calls the new method first, then calls the deprecated method, printing corresponding messages to the console. The program pauses briefly at the end to ensure that if Java Flight Recorder (JFR) is running, it has enough time to capture the invocation events, allowing developers to detect usage of deprecated methods during runtime.

2.2 Code Output

Calling new method...
New method called
Calling deprecated method...
Deprecated method called

The program output shows the sequence of method calls: it first prints messages related to the new method, then the deprecated method. When running with JFR enabled, these method invocations can be captured as events, helping developers monitor deprecated method usage in a live application without altering the program logic or relying solely on compile-time warnings.

2.3 Verifying JFR Captured the Deprecated Method Event

To confirm that the example actually triggers the jdk.OldDeprecatedMethod event, run the program with JFR enabled using the JVM options.

java -XX:StartFlightRecording=name=DeprecatedMethodRecording,settings=profile,dumponexit=true,filename=deprecated.jfr \
     -XX:FlightRecorderOptions:olddeprecatedmethod=true \
     -jar deprecatedexample.jar

After the application finishes, open the generated deprecated.jfr file in Java Mission Control (JMC) and inspect the event browser for jdk.OldDeprecatedMethod.

Event: jdk.OldDeprecatedMethod
--------------------------------
class      : DeprecatedExample
method     : oldMethod
descriptor : ()V
timestamp  : 2025-11-22 09:15:32
thread     : main
stacktrace :
  DeprecatedExample.oldMethod(DeprecatedExample.java:7)
  DeprecatedExample.main(DeprecatedExample.java:20)

This confirms that the JVM emitted a jdk.OldDeprecatedMethod event when oldMethod() was invoked, and that the event captured all essential details including the class name, method name, bytecode descriptor, timestamp, thread, and full stack trace at the call site, thereby verifying that the example works as expected and that JFR accurately records deprecated API usage during runtime.

3. Conclusion

In summary, using Java Flight Recorder (JFR) to detect invocations of deprecated methods provides a powerful and non-intrusive way to monitor legacy code usage during runtime. By capturing events related to deprecated method calls, developers can gain valuable insights into how and when outdated APIs are still being used in their applications. This approach complements compile-time warnings and helps guide refactoring efforts to improve code quality and maintainability over time.

4. Notes for JDK 22

In JDK 22, a new JFR event was added (named jdk.DeprecatedInvocation) to help detect invocations of deprecated methods located in the JDK. The event records which deprecated method was invoked, whether it is marked for removal, the invocation timestamp, and an associated stack trace so you can find the caller.

There are two common ways to capture JFR events: start a recording at JVM startup via JVM options, or use the JFR Java APIs (Recording/RecordingStream). For the jdk.DeprecatedInvocation event JDK authors made an important design choice: the event is produced only if a recording was requested at JVM start (i.e., with -XX:StartFlightRecording). In other words, starting a recording later (via jcmd or programmatic API) will typically not cause these events to be emitted unless the JVM was started with the recording flag — this avoids adding overhead when JFR is otherwise unused.

To ensure the jdk.DeprecatedInvocation events are reliably captured, start the JVM with the following command:

# recommended: start the JVM with a start-recording flag so the event is guaranteed to be emitted
java -XX:StartFlightRecording:name=DeprecatedCheck,settings=profile,dumponexit=true,filename=deprecated.jfr \
     -cp . DeprecatedInvocationDemo

4.1 Code Output

// File: DeprecatedInvocationDemo.java

import jdk.jfr.consumer.RecordingStream;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedStackTrace;
import jdk.jfr.consumer.RecordedStackFrame;

import java.util.Date;
import java.time.Duration;

public class DeprecatedInvocationDemo {

    public static void main(String[] args) throws Exception {
        // Start a RecordingStream on a background thread so we can simultaneously trigger deprecated calls.
        Thread streamThread = new Thread(() -> {
            try (RecordingStream rs = new RecordingStream()) {
                // Enable the JDK deprecated invocation event type
                rs.enable("jdk.DeprecatedInvocation");
                // Optionally limit to a short time window (safety)
                rs.onEvent("jdk.DeprecatedInvocation", (RecordedEvent ev) -> {
                    String method = ev.getString("method");          // e.g. java.util.Date.getYear()
                    boolean forRemoval = ev.getBoolean("forRemoval");
                    String ts = ev.getStartTime().toString();

                    System.out.println("[JFR] Deprecated invocation detected");
                    System.out.printf("  method: %s%n", method);
                    System.out.printf("  forRemoval: %b%n", forRemoval);
                    System.out.printf("  timestamp: %s%n", ts);

                    // Extract stack trace if present
                    RecordedStackTrace stackTrace = ev.getStackTrace();
                    if (stackTrace != null) {
                        System.out.println("  stackTrace:");
                        for (RecordedStackFrame f : stackTrace.getFrames()) {
                            System.out.printf("    %s %s:%d%n",
                                f.getMethod() != null ? f.getMethod().getType().getName() : "",
                                f.getMethod() != null ? f.getMethod().getName() : "",
                                f.getLineNumber());
                        }
                    } else {
                        System.out.println("  stackTrace: ");
                    }
                });

                // Keep the stream open for a while (or forever in real apps)
                rs.setMaxAge(Duration.ofMinutes(10));
                rs.setMaxSize(64 * 1024 * 1024L);
                rs.start(); // blocks until the stream is closed or interrupted
            } catch (Throwable t) {
                System.err.println("RecordingStream failed: " + t);
            }
        }, "jfr-stream-thread");
        streamThread.setDaemon(true);
        streamThread.start();

        // Small pause to ensure the stream is listening
        Thread.sleep(500);

        // Trigger a deprecated JDK method: java.util.Date.getYear() is deprecated
        System.out.println("Calling deprecated method java.util.Date.getYear()");
        Date d = new Date();
        // call deprecated method — typical candidate for jdk.DeprecatedInvocation
        @SuppressWarnings("deprecation")
        int y = d.getYear();
        System.out.println("Deprecated call returned: " + y);

        // Give the stream a moment to print the event
        Thread.sleep(1500);

        // In a real application you would close the stream or let the JVM exit naturally
        System.out.println("Demo complete.");
    }
}

4.1.1 Code Explanation

This Java program demonstrates how to use the Java Flight Recorder (JFR) RecordingStream API to detect invocations of deprecated methods at runtime. It starts by creating a background thread that opens a RecordingStream and enables the jdk.DeprecatedInvocation event, which listens for calls to deprecated JDK APIs. Inside the event handler, it extracts and prints details such as the method name, whether the method is marked for removal, the timestamp of the invocation, and the associated stack trace to identify where the deprecated method was called from. The stream is configured to remain active for up to 10 minutes or until explicitly stopped. Meanwhile, on the main thread, the program pauses briefly to ensure the event stream is active, then deliberately invokes the deprecated method java.util.Date.getYear() to trigger the event. The program suppresses deprecation warnings for this call and prints the returned result. After allowing some time for the event to be captured and printed, the program signals completion. This approach provides a practical way to monitor and diagnose deprecated API usage dynamically during program execution.

4.1.2 Code Output

Calling deprecated method java.util.Date.getYear()
Deprecated call returned: 125
[JFR] Deprecated invocation detected
  method: java.util.Date.getYear()
  forRemoval: false
  timestamp: 2025-11-30T23:01:28.431Z
  stackTrace:
    com.example.DeprecatedInvocationDemo main:54
    java.base/java.lang.Thread run:-1
Demo complete.

This output shows the event payload: the deprecated method signature, whether it’s marked for removal, a timestamp, and a stack trace showing the caller. The exact stack trace formatting depends on your JVM and JFR implementation.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Human
Human
4 days ago

Dear AI, the JFR event for tracking invocations of deprecated methods is called jdk.DeprecatedInvocation. Not jdk.OldDeprecatedMethod (where did you even learn that from?)

Markus
4 days ago

This article is full of errors to the extent it is misleading to users instead of helping them:

  1. The JFR event is not called jdk.OldDeprecatedMethod, but jdk.DeprecatedInvocation
  2. The global flag -XX:FlightRecorderOptions=olddeprecatedmethod=true does not even exist. In fact, specifying it on the command line, like is recommended in the article, prevents the JVM from starting due to an unrecognized option!
Markus
2 days ago
Reply to  Yatin Batra

What distribution has added this event in JDK 11?

Markus
2 days ago

Could you please list all supported suboptions for the flag “-XX:FlightRecorderOptions”?

Back to top button