Getting the Hwnd or Xid in JDK 21+

← Back to Blog

In modern versions of Java, it has became increasingly difficult to get the HWND or Xid of a Swing component. This is because of the encapsulation of the native peer in the Component class, as well as the module encapsulation changes introduced in Java 9.

Old methods of getting the HWND or Xid, such as using the sun.awt package, are no longer viable. For example, the top solution for this StackOverflow post (opens in a new tab) uses this solution, which doesn't even compile in JDK 21+.

import sun.awt.windows.WComponentPeer;
 
public static long getHWnd(Frame f) {
   return f.getPeer() != null ? ((WComponentPeer) f.getPeer()).getHWnd() : 0;
}

The implementation also only works on Windows JDK implementations, and doesn't work on Linux or MacOS. Other solutions, such as using JNA can grab the HWND or Xid, but it is not as simple as the above solution anymore, and it requires you to import another library for such a trivial task, which isn't really viable.

My Solution

My solution is to use Unsafe! The window long id is stored in the Component peer fields, but not accessible via normal Reflection because of accessibility rules. The only other way to retrieve it using Unsafe by first searching through all the fields, and finding the specific window id field.

First, grab the Unsafe instance via reflection.

  private static final Unsafe UNSAFE;
 
  static {
    try {
      final Field field = Unsafe.class.getDeclaredField("theUnsafe");
      field.setAccessible(true);
      UNSAFE = (Unsafe) field.get(null);
    } catch (final IllegalAccessException | NoSuchFieldException e) {
      throw new AssertionError(e);
    }
  }

The next step is to recursively get all the fields in the Component class, and find the field that is of type long

  public static List<Field> getAllFields(final Class<?> type) {
    final List<Field> fields = new ArrayList<>();
    for (Class<?> c = type; c != null; c = c.getSuperclass()) {
      final Field[] declared = c.getDeclaredFields();
      final List<Field> list = Arrays.asList(declared);
      fields.addAll(list);
    }
    return fields;
  }

On Windows, the field name is hwnd, and on Linux, the field name is window. Let's create a helper tool that determines the field name we are searching for.

  public static boolean isWindows() {
    return System.getProperty("os.name").toLowerCase().contains("win");
  }

Finally, let's get the window identifier from the ComponentPeer object via that field, and use Unsafe to access it.

  public static long getWindowHandle0(final Object component) {
    final String search = isWindows() ? "hwnd" : "window";
    final Class<?> clazz = component.getClass();
    final List<Field> fields = getAllFields(clazz);
    for (final Field field : fields) {
      final String name = field.getName();
      if (name.equals(search)) {
        final long offset = UNSAFE.objectFieldOffset(field);
        return UNSAFE.getLong(component, offset);
      }
    }
    return 0;
  }

We need to use Unsafe again to get the ComponentPeer since that is encapsulated as well.

  public static long getWindowHandle(final Component component) throws NoSuchFieldException {
    final Class<Component> clazz = Component.class;
    final Field field = clazz.getDeclaredField("peer");
    final long offset = UNSAFE.objectFieldOffset(field);
    final Object peer = UNSAFE.getObject(component, offset);
    return getWindowHandle0(peer);
  }

There we go! And that should work now reliably. Here is a testing code snippet to test it out.

  public static void main(String[] args) throws NoSuchFieldException {
    final JFrame frame = new JFrame();
    frame.setVisible(true);
    frame.setSize(400, 400);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
    final Canvas canvas = new Canvas();
    frame.add(canvas);
    frame.setVisible(true);
 
    System.out.println(getWindowHandle(frame));
    System.out.println(getWindowHandle(canvas));
  }

Here is the final code snippet:

import sun.misc.Unsafe;
 
import javax.swing.*;
import java.awt.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
public class WindowHandle {
 
  private static final Unsafe UNSAFE;
 
  static {
    try {
      final Field field = Unsafe.class.getDeclaredField("theUnsafe");
      field.setAccessible(true);
      UNSAFE = (Unsafe) field.get(null);
    } catch (final IllegalAccessException | NoSuchFieldException e) {
      throw new AssertionError(e);
    }
  }
 
  public static void main(final String[] args) throws NoSuchFieldException {
 
    final JFrame frame = new JFrame();
    frame.setVisible(true);
    frame.setSize(400, 400);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
    final Canvas canvas = new Canvas();
    frame.add(canvas);
    frame.setVisible(true);
 
    System.out.println(getWindowHandle(frame));
    System.out.println(getWindowHandle(canvas));
  }
 
  public static List<Field> getAllFields(final Class<?> type) {
    final List<Field> fields = new ArrayList<>();
    for (Class<?> c = type; c != null; c = c.getSuperclass()) {
      final Field[] declared = c.getDeclaredFields();
      final List<Field> list = Arrays.asList(declared);
      fields.addAll(list);
    }
    return fields;
  }
 
  public static long getWindowHandle0(final Object component) {
    final String search = isWindows() ? "hwnd" : "window";
    final Class<?> clazz = component.getClass();
    final List<Field> fields = getAllFields(clazz);
    for (final Field field : fields) {
      final String name = field.getName();
      if (name.equals(search)) {
        final long offset = UNSAFE.objectFieldOffset(field);
        return UNSAFE.getLong(component, offset);
      }
    }
    return 0;
  }
 
  public static boolean isWindows() {
    return System.getProperty("os.name").toLowerCase().contains("win");
  }
 
  public static long getWindowHandle(final Component component) throws NoSuchFieldException {
    final Class<Component> clazz = Component.class;
    final Field field = clazz.getDeclaredField("peer");
    final long offset = UNSAFE.objectFieldOffset(field);
    final Object peer = UNSAFE.getObject(component, offset);
    return getWindowHandle0(peer);
  }
}
© Brandon Li.