java-native-access/jna · error · RuntimeException
Failed to get a change handle
Error message
Failed to get a change handle - <errorCode>
What it means
monitorAllPrinters() calls FindFirstPrinterChangeNotification to obtain a change-notification handle for printer/job events. If the returned handle is not valid, the last error code is captured and a RuntimeException 'Failed to get a change handle - <errorCode>' is thrown. Without this handle the monitor cannot watch for printer changes.
Solutions
- Check errorCode: ERROR_ACCESS_DENIED (5) means run elevated or grant notification rights on the printer/server.
- Verify the print-server handle from OpenPrinter was valid before calling FindFirstPrinterChangeNotification.
- Validate the PRINTER_NOTIFY_OPTIONS fields/types match the loaded native structure (version/platform mismatches can corrupt the struct).
- Retry after confirming the spooler service is healthy; catch RuntimeException to avoid crashing the monitor loop.
Example fix
// before
HANDLE h = Winspool.INSTANCE.FindFirstPrinterChangeNotification(...);
// after
HANDLE h = Winspool.INSTANCE.FindFirstPrinterChangeNotification(...);
if (!isValidHandle(h)) {
int err = Kernel32.INSTANCE.GetLastError();
LOG.error("Change-handle registration failed (code " + err + "); check privileges/spooler");
return;
} Defensive patterns
Strategy: validation
Validate before calling
HANDLEByReference h = new HANDLEByReference();
if (!Winspool.INSTANCE.OpenPrinter(null, h, null)) {
throw new IllegalStateException("Cannot open print server: "
+ Kernel32.INSTANCE.GetLastError());
} Type guard
boolean isValidChangeHandle(HANDLE h) {
return h != null && !h.equals(WinBase.INVALID_HANDLE_VALUE);
} Prevention
- Always validate the OpenPrinter result before requesting a change handle.
- Run with privileges allowing printer change notification registration.
- Re-check handle validity each time before use.
- Keep PRINTER_NOTIFY_OPTIONS struct fields consistent with the JNA mapping version.
When it happens
Trigger: FindFirstPrinterChangeNotification returning INVALID_HANDLE_VALUE due to an invalid print-server handle, insufficient privileges for change notification, or a malformed PRINTER_NOTIFY_OPTIONS structure.
Common situations: Server handle was already closed or was null because a previous OpenPrinter failed silently; running without privileges to register change notifications on the spooler.
Related errors
- Failed to get printer change notification
- Failed to access the print server
- Win32Exception (error code from GetLastError after EnumJobs)
- Win32Exception (error code from GetLastError after…
- Could not set proxy blanket.
AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12).
Data as JSON: /api/errors/6ecdd35782f895d0.
Report an issue: GitHub.
Appendix: source
Thrown at contrib/w32printing/src/com/sun/jna/platform/win32/Win32SpoolMonitor.java:131
optionsType.Type = JOB_NOTIFY_TYPE;
optionsType.setFields(new short[] {
JOB_NOTIFY_FIELD_PRINTER_NAME,
JOB_NOTIFY_FIELD_STATUS,
JOB_NOTIFY_FIELD_DOCUMENT
});
optionsType.toArray(1);
options.pTypes = optionsType;
HANDLE changeNotificationsHandle =
Winspool.INSTANCE.FindFirstPrinterChangeNotification(
printServerHandle.getValue(),
PRINTER_CHANGE_ADD_JOB |
PRINTER_CHANGE_SET_JOB |
PRINTER_CHANGE_DELETE_JOB,
TWO_DIMENSIONAL_PRINTERS,
options);
if (!isValidHandle(changeNotificationsHandle)) {
int errorCode = Kernel32.INSTANCE.GetLastError();
throw new RuntimeException("Failed to get a change handle - " +
errorCode);
}
try {
while (true) {
Kernel32.INSTANCE.WaitForSingleObject(
changeNotificationsHandle,
WinBase.INFINITE);
DWORDByReference change =
new DWORDByReference();
PointerByReference infoPointer = new PointerByReference();
success =
Winspool.INSTANCE.FindNextPrinterChangeNotification(
changeNotificationsHandle,
change,
options,
infoPointer);View on GitHub (pinned to d036ad9781)