← Blog

The black window: bounding GPU textures in JavaFX

June 5, 2026 · Adrián De León · performance, javafx

This one only reproduced in the packaged build, which made it especially fun: open enough files, and eventually the whole editor window would go black. There was no exception in the echo area and no crash, only a black rectangle where the UI used to be.

The clue: it scaled with open files

The black window got likelier the more files you had open. That pointed away from any single document and toward something that accumulated. Digging into the render thread turned up NullPointerExceptions deep in JavaFX’s Prism pipeline, on a null RTTexture / mask texture.

That explained it. JavaFX’s Prism renderer keeps a texture pool with a fixed ceiling (512 MB by default). When you exhaust it, texture allocation starts returning null, and the render thread NPEs trying to use it, so the screen goes black. It only showed up packaged because the dev run had different VRAM headroom.

The fix: don’t let GPU resources grow with open files

The root problem was that GPU-backed resources scaled linearly with the number of open buffers. There were two big offenders.

As a safety net, the packaged launcher (and mvn javafx:run, so dev matches prod) also raises the caps: -Dprism.maxvram=2G -Dprism.maxTextureSize=16384.

Bounding per-buffer GPU resources

This is now a checklist item: any per-buffer Canvas or Image must be released or bounded. GPU memory isn’t garbage-collected the way heap is. A cached Image is a texture you’re holding until you let go. If a resource’s lifetime is “one per open file” and files can pile up, that’s a leak, and it will surface as a black window in a user’s packaged build rather than in your dev run.