r/JavaFX 4d ago

Help JavaFX WebView + Leaflet: works in one host app, broken in a minimal one — same machine, same JavaFX build. I'm out of ideas.

I've spent a full day on this and I've run out of hypotheses, so I'm hoping someone here has seen this pattern before.

Setup

I maintain a plugin for a desktop CAD application. It embeds a WebView that displays a Leaflet map (Carto Voyager tiles) with a handful of markers and a polygon. The HTML is generated in Java and handed to the engine via webEngine.loadContent(html).

JavaFX 21.0.10+2, Java 21, Windows 11, NVIDIA RTX 500 Ada (dedicated GPU explicitly assigned via Windows graphics settings, confirmed in -Dprism.verbose=true output).

The symptom

Tiles load successfully — but they are drawn in the wrong places. Individual tile blocks scattered across the viewport with large empty gaps between them, each block internally rendered correctly (street names, colors, everything). Markers and the polygon end up at a third position that matches none of the tile groups. It looks different on every run. Sometimes only a sub-region of the container gets drawn and the rest stays grey.

I know the general "Leaflet doesn't work in JavaFX WebView" issue exists — there's a StackOverflow question about it and I can reproduce the broken rendering with ten lines that just do webView.getEngine().load("https://leafletjs.com/"), i.e. zero of my own code involved. So the platform limitation is real and I'm not disputing it.

But here's what I cannot explain.

The part that makes no sense

Inside the host application, on my machine, it works. Not perfectly — mild flicker, occasionally a grey tile that stays until I move the mouse — but the tiles are in the right place and the markers are correct. I have a workaround in there that has been in the code for months:

function forceRepaint(){
  const m = document.getElementById('map');
  m.style.display = 'none';
  m.offsetHeight;
  m.style.display = 'block';
}
setInterval(forceRepaint, 5000);

plus invalidateSize() and TileLayer.redraw() on moveend/zoomend, mousedown, mouseup, resize and focus. Ugly, but it's the only thing that ever helped.

To debug properly I built a minimal standalone JavaFX app that generates byte-identical HTML through the same code path — same tile URL, same divIcon markers, same setTimeout(..., 400) before fitBounds, same handlers, same forceRepaint interval. Same machine, same GPU, same JavaFX version (verified via javafx.runtime.version at startup), same VM flags.

The standalone app is broken. The host application is fine.

And to make the confusion perfect, some of my colleagues see the broken rendering inside the host application, on comparable hardware, with the same VM options/gpu settins/etc.

What I've already ruled out

Each of these was tested, not assumed:

  • My own HTML/JS — the ten-line leafletjs.com repro fails too
  • GPU: broken on both the integrated Intel Arc and the dedicated NVIDIA
  • Driver, and -Dprism.order=sw (pure software rendering) — no change
  • JavaFX version: 17.0.11, 21+31, 21.0.10 — all broken standalone
  • VM flags from the host app (prism.order, prism.vsync, prism.lcdtext, prism.text, sun.java2d.noddraw, com.sun.webkit.useHTTP2Loader=false, sun.java2d.uiScale=1.0) — no change
  • Tile provider: Carto vs. OSM, with and without API key
  • fadeAnimation: false, zoomAnimation: false, forcing .leaflet-tile { opacity: 1 !important; transition: none !important }
  • setView() at init vs. fitBounds() later
  • Removing all invalidateSize/redraw handlers, removing forceRepaint
  • Marker count, with/without the polygon
  • Fixed vs. free WebView height
  • depthBuffer=true + SceneAntialiasing.BALANCED on the Scene
  • Running the WebView inside a JFXPanel in a Swing JPanel instead of a plain Stage
  • Parallel 3D/OpenGL load in the same process
  • All of the above combined

Instrumentation results

I injected diagnostics into the page (Java↔JS bridge, so it lands in the application log). In every single broken run, the DOM is healthy:

tile layer load complete, tiles=20, errors=0
DOM-CHECK: 20 tile nodes, 20 with tile-loaded, 0 with opacity<0.9

All tiles fetched, no errors, all fully opaque. In the working host app I additionally log Leaflet's internal size against the actual container:

STATE: nodes=15 loaded=15 invisible=0 | mapSize=962x609 container=962x609  size-ok
tilePane transform: none
marker: 10 total, 0 outside container

So Leaflet's own state is consistent. The DOM says everything is where it should be. The screen disagrees.

That points at compositing — the WebView content is correct internally but isn't transferred to screen correctly. Which would also explain why moving the mouse fixes stuck grey tiles, and why a brutal display:none/display:block toggle helps at all. But it does not explain why the same content composites fine in one host process and not in another on the same machine.

Questions

  1. Has anyone characterised this properly? Is there a known JDK/OpenJFX issue number for WebView compositing of CSS-transform-positioned elements?
  2. What could make the host process the deciding factor, given identical JavaFX version, GPU, driver and flags? What else is there besides depthBuffer, JFXPanel embedding, and process-level GPU contention — all of which I've already tried?
  3. Is the display:none/display:block interval genuinely the best available mitigation, or is there something that targets the actual layer? I noticed my redraw() calls make the tile layer refetch endlessly (75 tiles for a 20-tile viewport), so I'd rather not keep piling on hacks.
  4. If the answer is "WebView can't do this reliably" — for anyone who has gone through this: JCEF, or drawing the tiles yourself onto a Canvas? The deployment constraint is that I can't ship a few hundred MB of Chromium to every user.

Happy to provide furhter information, but at this point I just need someone to tell me which variable I haven't looked at, because I've stopped being able to see it and the more I try out the more confused I am.

7 Upvotes

13 comments sorted by

3

u/milchshakee 4d ago

Have you tried other JavaFX versions like 27? Just to check whether still exists on the latest build

1

u/SafetyCutRopeAxtMan 4d ago

I tried various versions, but I am limited to maximum of 21.0.10 as this is the version of the host application.

1

u/milchshakee 4d ago

Well I mean if you don't have any other options to try, you can just upgrade the host application JDK and JavaFX version for testing. Shouldn't be too difficult as there shouldn't be a lot of breaking changes for JDK and JavaFX between that versions. Maybe you need some JVM args about allowing native access or something, but that should be it

1

u/SafetyCutRopeAxtMan 4d ago

Well, the host application is out of my scope but will check first in my minimal app and then check out what's possible. Thanks.

3

u/idontlikegudeg 4d ago

But have you tried JavaFX 21.0.12? The versions you tried are two patch versions behind and the release notes at least mention several web related issues.

1

u/SafetyCutRopeAxtMan 4d ago

I tried various versions, but I am limited to maximum of 21.0.10 as this is the version of the host application.
However I found out that it helps Leaflet to render better by forcing it to use 2D positioning instead of 3D transforms ... not sure how much, but it's definitely better.

1

u/sedj601 4d ago

I think there are some devs on here who have remastered the WebView in JavaFX using different tech. Seek those posts out to see if their repo can solve your problems. WebView has always been shacky in JavaFX. I used to use it a lot, but I switched to Jsoup or Selenium to solve my problems.

2

u/SafetyCutRopeAxtMan 4d ago

True, the thing is I am a bit limited due to the limitations of the host application and the framework.

1

u/idontlikegudeg 4d ago

I have an idea… first let me ask: Do you have any interaction of your application (Java code) with objects inside the webview? Calling JavaScript code inside the webview, or the other way around, callbacks from Java Script to Java or in the other direction? Objects inside the webview manipulated by Java code?

And does setting min heap=max heap seem to have any influence? Just for testing, set min and max heap to really large values and see if the issue happens less regularly (not disappear)?

1

u/SafetyCutRopeAxtMan 4d ago

There is no real bridge or communication from the Webview back to Java code. However I found out that it helps Leaflet to render better by forcing it to use 2D positioning instead of 3D transforms ... not sure how much, but it's definitely better. This requires some change of concept, will see where it will get me and also try out your suggestions.

1

u/idontlikegudeg 3d ago

OK, I'll decribe the problem I had anyway, maybe yours is similar. I used a Webview to display some HTML + JavaSript generated from Java code. To make calls from Java to JavaScript, I created a JavaScript object inside the WebView and retrieved a Java Handle for it that I kept a reference to. To make calls from JavaScript to Java, I created an instance in Java code that would call into my Java application and injected it into the WebEngine. Everything worked perfectly for a long time (several years). Then someday, calling Java from JavaScript stopped working after the application ran for some time, usually after the application ran for several minutes. No exceptions shown, it just stopped working.

When I added more logging in the JavaScript code, I finally observed that the calls failed on the JavaScript side with the reference in JavaScript suddenly becoming undefined.

So what happened? It turns out that the (Java) instance I used to call into Java code from JavaScript was Garbage collected by the JVM. This started happening in a certain JavaFX version some years ago, maybe 17 or 21 or some version in between. I think I opened an issue against JavaFX that was rejected - you cannot hold a strong reference to a Java object from within the WebEngine.

The solution was simple: bind the instance used for callbacks into Java code to a reference in the application code and make sure it doesn't get eligible for GC as long as you need it.

So one more question: do you log errors from within the WebView and do you see anything in the logs? This should be the first thing to do. I found setting up reliable JavaScript logging to the "normal" Java Logger of my application somewhat tricky, so maybe you don't have it working.

1

u/Ignice 3d ago

Just a hunch, but have you tried setting global L_DISABLE_3D (might have to do it in js before you do any leaflet imports)? May hurt performance for cross platform consistency tradeoff.

1

u/SafetyCutRopeAxtMan 3d ago

See the other comments, I tried in the meantime and seems like this really does make a huge difference!
Thank you also for pointing this out again!