Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

For a screenshot of one Windows window, the shortest reliable JNA route is com.sun.jna.platform.win32.GDI32Util.getScreenshot(HWND). Pass the target window’s handle, check for a null result, and save the returned BufferedImage with Java’s image APIs. If you need control over device contexts and bitmap allocation, use JNA’s lower-level GDI32.BitBlt binding instead.

This guide covers the supported window-capture path, resource and failure handling, a direct GDI implementation outline, and the cases these APIs do not establish—such as complete desktop capture, minimized windows, layered overlays, cursor inclusion, multi-monitor coordinates, and DPI behavior.

What you need before writing code

  • A Windows application built with Java.
  • JNA and the JNA Platform artifact on the runtime class path, using versions that match your project. The screenshot helper is in com.sun.jna.platform.win32.
  • An HWND for the window you want to capture. This is the native Windows handle, not a Java java.awt.Window reference by itself.
  • Permission to write the output file and a Java runtime with java.awt.image.BufferedImage available.

Because the current API reference is versioned, check the JNA release documentation and your build tool before pinning a dependency version. Do not copy a version number from an unrelated project.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use GDI32Util for a known window

GDI32Util.getScreenshot(HWND) is the high-level helper. It acquires the window’s device context, creates the required bitmap, copies pixels, and returns a BufferedImage. The documented contract is window-targeted: you provide an HWND, and the result represents that window.

Complete capture-and-save class

import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.GDI32Util;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Path;

public final class WindowScreenshot {
    private WindowScreenshot() {}

    public static void save(HWND hwnd, Path output) throws IOException {
        if (hwnd == null) {
            throw new IllegalArgumentException("hwnd must not be null");
        }

        BufferedImage image = GDI32Util.getScreenshot(hwnd);
        if (image == null) {
            throw new IOException("Windows returned no image for the supplied window");
        }

        String name = output.getFileName().toString().toLowerCase();
        String format;
        if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {
            format = "jpg";
        } else if (name.endsWith(".bmp")) {
            format = "bmp";
        } else {
            format = "png";
        }
        if (!ImageIO.write(image, format, output.toFile())) {
            throw new IOException("No ImageIO writer for format " + format);
        }
    }
}

Call WindowScreenshot.save(hwnd, Path.of("window.png")) after your program has obtained the native handle. For a JPEG or BMP, use the corresponding extension; the example chooses PNG for every other extension.

Check the documented failure cases

The helper can return null when it cannot construct an image. Its Javadoc also documents IllegalStateException when the window rectangle has zero width or height, and when the acquired device context cannot be released. Treat either condition as a capture failure, log the window identity and dimensions, and avoid writing a partially initialized image.

try {
    WindowScreenshot.save(hwnd, Path.of("capture.png"));
} catch (IllegalStateException e) {
    // The target may have no drawable area, or native cleanup failed.
    System.err.println("Window capture state error: " + e.getMessage());
} catch (IOException e) {
    System.err.println("Could not save capture: " + e.getMessage());
}

How to obtain and validate the HWND

Your application may already receive an HWND through another JNA User32 call or native integration. Keep that handle associated with the window you intend to capture, and validate it immediately before capture because windows can close or recreate their native handle.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Do not pass a null handle.
  • Read the target window’s rectangle and reject a zero width or height before calling the helper.
  • Capture on a thread where your application can handle a native failure without losing the rest of the UI.
  • If the window closes between validation and capture, treat a null image or state exception as an expected race and reacquire the handle.

The supplied API references do not establish a universal recipe for converting every Swing, JavaFX, or AWT window to an HWND. Use the handle-creation method appropriate to your UI toolkit and verify it against the JNA version in your build.

When to use direct GDI calls through JNA

Use the lower-level route when you need to control the native bitmap and device-context lifecycle yourself. Microsoft’s Win32 capture workflow is: obtain a source device context, create a compatible destination device context and bitmap, select the bitmap into the destination, copy pixels with BitBlt, then release and delete every native resource in the correct order.

The essential operation

JNA’s GDI32 interface maps BitBlt. Microsoft describes BitBlt as copying color data from one device context to another; the SRCCOPY raster operation copies source pixels directly.

// Conceptual sequence; use the exact structures and signatures from
// the GDI32 and User32 interfaces supplied by your JNA version.
HDC source = user32.GetWindowDC(hwnd);
HDC destination = gdi32.CreateCompatibleDC(source);
HBITMAP bitmap = gdi32.CreateCompatibleBitmap(source, width, height);
HGDIOBJ previous = gdi32.SelectObject(destination, bitmap);
try {
    boolean copied = gdi32.BitBlt(destination, 0, 0, width, height,
        source, 0, 0, WinGDI.SRCCOPY);
    if (!copied) {
        // Query the native error and fail the capture.
    }
    // Convert the bitmap to a BufferedImage before releasing it.
} finally {
    gdi32.SelectObject(destination, previous);
    gdi32.DeleteObject(bitmap);
    gdi32.DeleteDC(destination);
    user32.ReleaseDC(hwnd, source);
}

The snippet shows the lifecycle, not a drop-in replacement for GDI32Util: bitmap-to-BufferedImage conversion requires the bitmap metadata and pixel buffer definitions used by your JNA version. Every successful acquisition must have a matching release, including error paths. Leaking device contexts or GDI objects can eventually make later captures fail.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choosing between the two approaches

Approach Best use Trade-off and scope
GDI32Util.getScreenshot(HWND) A straightforward screenshot of a known window Small API surface; documented null and illegal-state failure cases
Direct JNA GDI32 calls Applications needing control of device contexts, bitmap allocation, or copy steps More native-resource management and bitmap-conversion code

The available references do not establish a performance, image-quality, DPI, or compatibility winner. Select the helper unless you have a concrete requirement for lower-level control.

What this method does—and does not—promise

Window scope

The helper accepts an HWND, so its documented target is one window. Do not label this code a verified whole-desktop or all-monitor capture implementation.

Occlusion, minimization, overlays, and the cursor

The cited API material does not guarantee behavior for minimized or occluded windows, layered overlays, pop-up surfaces, or mouse-cursor inclusion. Test those cases on the Windows versions and rendering technologies your application supports instead of assuming that a normal visible window result covers them.

Multiple monitors and DPI

JNA’s User32 reference exposes virtual-screen metric identifiers, but that reference is not a complete multi-monitor or DPI-awareness guide. Coordinates can therefore require application-specific verification, especially when monitors use different scale factors. Confirm the process DPI-awareness mode, rectangle units, and bitmap dimensions in your own target environment.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting checklist

“The method returns null”

  • Confirm that the HWND is valid and still belongs to a live window.
  • Check that the window rectangle is nonzero and that the process can access it.
  • Retry after reacquiring the handle if the window was recreated.

IllegalStateException mentions size or device-context release

  • For a zero-size rectangle, wait until the window is created and laid out, then measure again.
  • For release failure, stop reusing the suspect native state, record the exception, and test with the high-level helper before maintaining custom GDI code.

The file is created but cannot be opened

  • Check the output directory and process permissions.
  • Use an extension that matches a writer supported by ImageIO, and check the boolean result from ImageIO.write.
  • Do not overwrite the file until a non-null image has been obtained.

Black, clipped, or unexpected pixels

  • Verify the source rectangle and device-context origin.
  • Check whether the window is minimized, covered, layered, or rendered by a technology whose capture behavior you have not tested.
  • For multi-monitor setups, log the rectangle, scale factor, and resulting bitmap dimensions.

Captures fail after many iterations

Audit every native acquisition and release in a finally block. Direct GDI code that leaves device contexts, selected bitmaps, or GDI objects allocated can exhaust native resources.

Performance, reliability, and cost considerations

Neither the cited JNA references nor the Win32 page supplies a capture-speed benchmark or failure-rate statistic, so avoid promising a frame rate or latency. For repeated captures, reuse ordinary Java objects where practical, keep output I/O off the UI thread, and measure on the actual window sizes, monitor layouts, and DPI modes you support. The high-level helper reduces code you must maintain; direct GDI gives control at the cost of more cleanup paths.

A local JNA capture has no ScreenshotNeo request cost, but it also leaves window discovery, native cleanup, file encoding, and edge-case testing in your application.

Or skip the browser setup

If your real goal is a website image rather than a native Windows window, ScreenshotNeo provides a single HTTP request. It accepts the page as a URL and returns PNG, JPEG, WebP, or PDF. Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, failed loads, and timeouts are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also exposes an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

See the parameter reference and additional capture options in the ScreenshotNeo documentation. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to get an API key.

Frequently Asked Questions

Can I pass a Java AWT Window directly to GDI32Util?

No. The helper’s parameter is a native Win32 HWND. Obtain and validate that handle through the UI toolkit or JNA integration you use, then pass the HWND.

Should I choose PNG or JPEG for the saved image?

PNG preserves lossless pixels and is the safest default for UI screenshots. JPEG can reduce file size but introduces lossy compression; choose it only when that trade-off suits your output.

Is direct BitBlt code required for a normal window screenshot?

No. Start with GDI32Util.getScreenshot(HWND). Implement BitBlt yourself only when you need control over native bitmap and device-context steps.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.