<div dir="ltr">Hi John,<div><br></div><div>Thanks, I have made changes to Parent before (although I seem to have lost the exact changes I made) and that seemed to work, but I couldn't do that for mobile. I wanted to get to the bottom of this before I said anything, but I haven't made too much progress. It's taken 3 months for the issue to reappear for a user since I put in the code to try fix the scene graph.</div><div><br></div><div>As you state, it's very easy for developers to add a listener onto something and make changes when they shouldn't. We learned that the hard way in particular when listening to the scene or parent of nodes. Now we do Platform.runLater for that code even if we are in the app thread to let it complete whatever it was doing before we start making changes. It's entirely possible that there are other cases like this that we missed. </div><div><br></div><div>Dean</div></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Wed, Jun 25, 2025 at 3:40 PM John Hendrikx <<a href="mailto:john.hendrikx@gmail.com">john.hendrikx@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><u></u>

  
    
  
  <div>
    <p>I've looked at the code as well, and I think I agree with your
      assessment.  `getChildTransformedBounds` does a lot of stuff, and
      will modify properties that can be observed while the
      `dirtyChildren` list is being used in `updateCachedBounds`.  <br>
    </p>
    <p>However, what I think the problem is directly with the
      `remainingDirtyNodes` counter.   This is an optimization to avoid
      checking all children if the `children` list is passed (instead of
      `dirtyChildren`) to updateCachedBounds.  However, if while
      iterating LESS children are found with their bounds changed
      (because some of them were fixed already) then the
      `remainingDirtyNodes` counter can no longer be correct, and the
      loop continues until it hits negative indices.</p>
    <p>I see a few possible solutions:</p>
    <p>- Simply add `i >= 0` as a condition to the `for` loop, and
      using `remainingDirtyNodes` more as a "rough indication" instead
      of an exact truth; the reason why I think that's a reasonable fix
      is that even without using `remainingDirtyNodes` the loop would
      function correctly (albeit perhaps slower in some cases as it
      checks too much).  The optimization here is assuming that children
      are usually appended at the end of the list (which is not always
      the case) and once you have encountered X dirty nodes, you can
      exit early, avoiding having to iterate potentially large children
      lists fully.</p>
    <p>- Completely remove `remainingDirtyNodes` and
      `dirtyChildrenCount` -- this would potentially slow down
      updateCachedBounds for nodes with many children (likely
      unnoticable though until you hit 1000+ children under a single
      node -- something to be avoided for far more reasons than just
      this code).  The loop would then simply check all children (or
      only the dirtyChildren).</p>
    <p>Although I'm very sure these solutions will made the IOOBE
      disappear, I'm not 100% sure it may not expose a new problem (a
      node with perhaps bounds that weren't updated) -- however, seeing
      this new problem will allow us to actually work on a solution
      instead of not even getting to that point because of an exception
      that cripples the whole application with no recourse...</p>
    <p>Making a copy of the Parent class (in its original package) and
      including it in your project (perhaps having to open some modules
      or disable the module system) and adding an `i >= 0` condition
      should probably resolve this problem if you want to experiment
      with this solution.  This is how I usually debug problems and try
      out solutions without having to build a complete new set of FX
      artifacts.</p>
    <p>I tried making defensive copies of the List passed in, but this
      was insufficient (as the problem is not with the list becoming out
      of date, but with the bounds changed flag changing without a
      corresponding update to `remainingDirtyNodes`).<br>
    </p>
    <p>--John<br>
    </p>
    <p>On 25/06/2025 11:06, Dean Wookey wrote:</p>
    <blockquote type="cite">
      
      <div dir="ltr">
        <ol style="margin:0px;list-style:none;padding:0px;color:rgb(23,43,77);font-family:"DejaVu Sans",sans-serif;font-size:14px">
          Hi Everyone,<br>
          <br>
          We've also been experiencing this problem over the years. It
          seems to be related to JDK-8198577.<br>
          <br>
          Once it goes wrong, each pulse hits the issue repeated meaning
          it can never escape. It's rare, but extremely disruptive when
          it does occur because the user loses what they've been working
          on and has to restart the app.<br>
          <br>
          I've tried really hard to figure out the conditions this
          happens in. I don't think it's a multiple thread issue
          (although for some people it almost certainly could be
          triggered that way) because we've put conditional breakpoints
          that trigger whenever anything that could affect dirty
          children is done off the app thread. We've got assert
          Platform.isFXApplicationThread() all over our app to make sure
          the threading is happening properly.<br>
          <br>
          What I think is happening is that getChildTransformedBounds
          which is being called inside the updateCachedBounds loop, can
          in some rare cases, end up triggering a call to
          updateCachedBounds on the same node. Basically
          updateCachedBounds can call itself recursively. This is a
          snipped from Parent.java in updateCachedBounds.<br>
          <br>
          // this checks the newly added nodes first, so if dirtyNodes
          is the<br>
          // whole children list, we can end early<br>
          for (int i = dirtyNodes.size() - 1; remainingDirtyNodes >
          0; --i) {<br>
             final Node node = dirtyNodes.get(i);<br>
             if (node.boundsChanged) {<br>
                 // assert node.isVisible();<br>
                 node.boundsChanged = false;<br>
                 --remainingDirtyNodes;<br>
                 tmp = getChildTransformedBounds(node,
          BaseTransform.IDENTITY_TRANSFORM, tmp);<br>
          <br>
          In the code above, if this gets called recursively through
          getChildTransformedBounds, then node.boundsChanged will change
          to false for all the nodes which stops remainingDirtyNodes
          from being updated and i eventually goes negative.<br>
          <br>
          We tried to fix the scene graph when this happens by catching
          the exception in the Thread.setDefaultUncaughtExceptionHandler
          but it didn't work. Maybe Christopher's suggested fix would
          work, but as Kevin says "It needs to be tested to ensure that
          when we get the AIOOBE that we can recover. It wouldn't solve
          anything if we catch and log that exception only to have it
          fail shortly after because the scene graph isn't in a good
          state (I don't know whether that would be the case, but it's
          something that needs to be checked)."<br>
          <br>
          Here's how we tried to fix the scene graph when we caught the
          error. The "Fixing IOB Issue" log gets hit all the time, but
          it doesn't find any problems, and in the next pulse it hits
          the problem again with various different stack traces until it
          settles on one. In our latest example of the error, it first
          occurred during a Platform.runLater and not during the pulse,
          but then all subsequent issues happen during the pulse.<br>
          <br>
              protected static void checkSpecialException(Throwable t) {<br>
                  if (t instanceof IndexOutOfBoundsException) {<br>
                      fixIndexOutOfBounds(t);<br>
                  }<br>
              }<br>
          <br>
              public static void fixIndexOutOfBounds(Throwable
          throwable) {<br>
                  FXUtilities.log(EmbraceDesktop.class, <a href="http://org.slf4j.event.Level.INFO" target="_blank">org.slf4j.event.Level.INFO</a>,
          "Fixing IOB Issue");<br>
                  try {<br>
                      Field dirtyChildrenCountField =
          Parent.class.getDeclaredField("dirtyChildrenCount");<br>
                      dirtyChildrenCountField.setAccessible(true);<br>
                      Field dirtyChildrenField =
          Parent.class.getDeclaredField("dirtyChildren");<br>
                      dirtyChildrenField.setAccessible(true);<br>
                      Set<Scene> apps =
          applicationManager.getApplications();<br>
                      ArrayList<Node> brokenStack = new
          ArrayList<>();<br>
                      for (Scene s: apps) {<br>
                          fixTreeRecursive(dirtyChildrenCountField,
          dirtyChildrenField, s.getRoot(), brokenStack);<br>
                      }<br>
                      if (brokenStack.size() > 0) {<br>
                          StringBuilder errorStack = new
          StringBuilder();<br>
                          for (Node n: brokenStack) {<br>
                             
          errorStack.append(n.getClass().getSimpleName() + " " +
          String.join( ",", n.getStyleClass())).append("\n");<br>
                          }<br>
                          EmbraceAnalytics.logCrash("Index out of bounds
          crash",errorStack.toString(), throwable);<br>
                      }<br>
          <br>
          <br>
                  }<br>
                  catch (Throwable t2) {<br>
                      FXUtilities.log(EmbraceDesktop.class,
          org.slf4j.event.Level.ERROR, "Exception while fixing tree",
          t2);<br>
                  }<br>
              }<br>
          <br>
              protected static boolean fixTreeRecursive(Field
          dirtyChildrenCountField, Field dirtyChildrenField, Parent
          parent, ArrayList<Node> brokenStack) throws
          IllegalAccessException {<br>
                  List<?> dirtyChildren = (List<?>)
          dirtyChildrenField.get(parent);<br>
                  int dirtyChildrenCount = (int)
          dirtyChildrenCountField.get(parent);<br>
                  if (dirtyChildren != null) {<br>
                      if (dirtyChildrenCount > dirtyChildren.size())
          {<br>
                          FXUtilities.log(EmbraceDesktop.class,
          org.slf4j.event.Level.ERROR, "Offending node1 was " +
          parent.getClass().getSimpleName());<br>
                          dirtyChildrenCountField.set(parent,
          dirtyChildren.size());<br>
                          brokenStack.add(parent);<br>
                          return true;<br>
                      }<br>
                  }<br>
                  else {<br>
                      if (parent.getChildrenUnmodifiable().size() <
          dirtyChildrenCount) {<br>
                          FXUtilities.log(EmbraceDesktop.class,
          org.slf4j.event.Level.ERROR, "Offending node2 was " +
          parent.getClass().getSimpleName());<br>
                          dirtyChildrenCountField.set(parent,
          parent.getChildrenUnmodifiable().size());<br>
                          brokenStack.add(parent);<br>
                          return true;<br>
                      }<br>
                  }<br>
                  for (Node n: parent.getChildrenUnmodifiable()) {<br>
                      if (n instanceof Parent) {<br>
                          boolean error =
          fixTreeRecursive(dirtyChildrenCountField, dirtyChildrenField,
          (Parent)n, brokenStack);<br>
                          if (error) {<br>
                              brokenStack.add(parent);<br>
                              FXUtilities.log(EmbraceDesktop.class,
          org.slf4j.event.Level.ERROR, "Parent was " +
          parent.getClass().getSimpleName());<br>
                          }<br>
                          return error;<br>
                      }<br>
                  }<br>
                  return false;<br>
              }<br>
          <br>
          I think we should we should put the index check potential fix
          in and log when it happens. As far as we can tell, if this
          issue gets hit, it's catastrophic 100% of the time. The fix
          might resolve the issue. It can't really make it any worse.
          Another thing we should do is add a check for recursive entry
          to that method and log when that occurs. That's (I think) the
          real issue, and without a stack trace of that, it's hard to
          find the root cause.<br>
          <br>
          I don't know if anyone else has experienced this issue and has
          insights/workarounds?<br>
          <br>
          Dean<br>
        </ol>
      </div>
      <br>
      <div class="gmail_quote">
        <div dir="ltr" class="gmail_attr">On Mon, Mar 24, 2025 at
          5:22 PM Christopher Schnick <<a href="mailto:crschnick@xpipe.io" target="_blank">crschnick@xpipe.io</a>>
          wrote:<br>
        </div>
        <blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
          <div>
            <p>Hello,<br>
              <br>
              We encountered an issue after updating our application
              implementation to frequently change the visibility of
              nodes. We are essentially now running an implementation
              that very frequently changes the visibility of various
              children nodes based on when they are needed and shown.
              When the user performs a lot of actions, the visibility of
              many nodes will be changed rapidly. <br>
              <br>
              For that, there are many listeners in place that listen
              for bounds changes of nodes to recheck whether they need
              to be made visible or not. All the visibility changes are
              queued up, so they are not immediately done in the
              listener after any bounds changes of parents. They are all
              properly done on the platform thread with runLater. When
              this implementation is running on many client systems, we
              sometimes receive an error report with an exception that
              looks something like this:<br>
              <br>
              java.lang.IndexOutOfBoundsException: Index -1 out of
              bounds for length 2<br>
                  at
java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:100)<br>
                  at
java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:106)<br>
                  at
java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:302)<br>
                  at
              java.base/java.util.Objects.checkIndex(Objects.java:365)<br>
                  at
              java.base/java.util.ArrayList.get(ArrayList.java:428)<br>
                  at
              <a href="mailto:javafx.base@25-ea/com.sun.javafx.collections.ObservableListWrapper.get" target="_blank">javafx.base@25-ea/com.sun.javafx.collections.ObservableListWrapper.get</a>(ObservableListWrapper.java:88)<br>
                  at
              <a href="mailto:javafx.base@25-ea/com.sun.javafx.collections.VetoableListDecorator.get" target="_blank">javafx.base@25-ea/com.sun.javafx.collections.VetoableListDecorator.get</a>(VetoableListDecorator.java:326)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/javafx.scene.Parent.updateCachedBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.Parent.updateCachedBounds</a>(Parent.java:1769)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/javafx.scene.Parent.recomputeBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.Parent.recomputeBounds</a>(Parent.java:1713)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/javafx.scene.Parent.doComputeGeomBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.Parent.doComputeGeomBounds</a>(Parent.java:1566)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/javafx.scene.Parent$1.doComputeGeomBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.Parent$1.doComputeGeomBounds</a>(Parent.java:116)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.javafx.scene.ParentHelper.computeGeomBoundsImpl" target="_blank">javafx.graphics@25-ea/com.sun.javafx.scene.ParentHelper.computeGeomBoundsImpl</a>(ParentHelper.java:84)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.javafx.scene.layout.RegionHelper.superComputeGeomBoundsImpl" target="_blank">javafx.graphics@25-ea/com.sun.javafx.scene.layout.RegionHelper.superComputeGeomBoundsImpl</a>(RegionHelper.java:78)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.javafx.scene.layout.RegionHelper.superComputeGeomBounds" target="_blank">javafx.graphics@25-ea/com.sun.javafx.scene.layout.RegionHelper.superComputeGeomBounds</a>(RegionHelper.java:62)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/javafx.scene.layout.Region.doComputeGeomBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.layout.Region.doComputeGeomBounds</a>(Region.java:3301)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/javafx.scene.layout.Region$1.doComputeGeomBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.layout.Region$1.doComputeGeomBounds</a>(Region.java:166)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.javafx.scene.layout.RegionHelper.computeGeomBoundsImpl" target="_blank">javafx.graphics@25-ea/com.sun.javafx.scene.layout.RegionHelper.computeGeomBoundsImpl</a>(RegionHelper.java:89)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.javafx.scene.NodeHelper.computeGeomBounds" target="_blank">javafx.graphics@25-ea/com.sun.javafx.scene.NodeHelper.computeGeomBounds</a>(NodeHelper.java:101)<br>
                  at <a href="mailto:javafx.graphics@25-ea/javafx.scene.Node.updateGeomBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.Node.updateGeomBounds</a>(Node.java:3908)<br>
                  at <a href="mailto:javafx.graphics@25-ea/javafx.scene.Node.getGeomBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.Node.getGeomBounds</a>(Node.java:3870)<br>
                  at <a href="mailto:javafx.graphics@25-ea/javafx.scene.Node.getLocalBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.Node.getLocalBounds</a>(Node.java:3818)<br>
                  at <a href="mailto:javafx.graphics@25-ea/javafx.scene.Node.updateTxBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.Node.updateTxBounds</a>(Node.java:3972)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/javafx.scene.Node.getTransformedBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.Node.getTransformedBounds</a>(Node.java:3764)<br>
                  at <a href="mailto:javafx.graphics@25-ea/javafx.scene.Node.updateBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.Node.updateBounds</a>(Node.java:828)<br>
                  at <a href="mailto:javafx.graphics@25-ea/javafx.scene.Parent.updateBounds" target="_blank">javafx.graphics@25-ea/javafx.scene.Parent.updateBounds</a>(Parent.java:1900)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/javafx.scene.Scene$ScenePulseListener.pulse" target="_blank">javafx.graphics@25-ea/javafx.scene.Scene$ScenePulseListener.pulse</a>(Scene.java:2670)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.javafx.tk.Toolkit.runPulse" target="_blank">javafx.graphics@25-ea/com.sun.javafx.tk.Toolkit.runPulse</a>(Toolkit.java:380)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.javafx.tk.Toolkit.firePulse" target="_blank">javafx.graphics@25-ea/com.sun.javafx.tk.Toolkit.firePulse</a>(Toolkit.java:401)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulse" target="_blank">javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulse</a>(QuantumToolkit.java:592)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulse" target="_blank">javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulse</a>(QuantumToolkit.java:572)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue" target="_blank">javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue</a>(QuantumToolkit.java:565)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$6" target="_blank">javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$6</a>(QuantumToolkit.java:346)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run$$$capture" target="_blank">javafx.graphics@25-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run$$$capture</a>(InvokeLaterDispatcher.java:95)<br>
                  at
              <a href="mailto:javafx.graphics@25-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run" target="_blank">javafx.graphics@25-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run</a>(InvokeLaterDispatcher.java)<br>
                  <br>
              The index out of bounds is not always the same, there are
              various variations of this. It happens on all operating
              systems. It seems like there is a very specific scenario
              where an index can be out of bounds. This happens very
              rarely, like only a few times out of some hundred
              application runs, so I tried my best at forcing it to
              reproduce.<br>
              <br>
              The following reproducer works most of the time, but it
              might have to be run multiple times. I am aware that it
              eventually results in a StackOverflow, but that was the
              best way to force it reliably, by just continuously
              spamming visibility changes to eventually encounter this
              rare issue. But I want to emphasize that the same error
              also occurs naturally when not being forced like this, but
              it is just a lot more rare. So the StackOverflow in the
              reproducer has nothing to do with this issue, it also
              happens later on.</p>
            <div style="background-color:rgb(30,31,34);color:rgb(188,190,196)">
              <pre style="font-family:"JetBrains Mono",monospace"><span style="color:rgb(207,142,109)">import </span>javafx.application.Application;
<span style="color:rgb(207,142,109)">import </span>javafx.scene.Scene;
<span style="color:rgb(207,142,109)">import </span>javafx.scene.control.Button;
<span style="color:rgb(207,142,109)">import </span>javafx.scene.layout.Region;
<span style="color:rgb(207,142,109)">import </span>javafx.scene.layout.StackPane;
<span style="color:rgb(207,142,109)">import </span>javafx.scene.layout.VBox;
<span style="color:rgb(207,142,109)">import </span>javafx.stage.Stage;

<span style="color:rgb(207,142,109)">import </span>java.io.IOException;

<span style="color:rgb(207,142,109)">public class </span>ParentBoundsBug <span style="color:rgb(207,142,109)">extends </span>Application {

    <span style="color:rgb(179,174,96)">@Override
</span><span style="color:rgb(179,174,96)">    </span><span style="color:rgb(207,142,109)">public void </span><span style="color:rgb(86,168,245)">start</span>(Stage stage) <span style="color:rgb(207,142,109)">throws </span>IOException {
        Scene scene = <span style="color:rgb(207,142,109)">new </span>Scene(createContent(), <span style="color:rgb(42,172,184)">640</span>, <span style="color:rgb(42,172,184)">480</span>);
        stage.setScene(scene);
        stage.show();
        stage.centerOnScreen();
    }

    <span style="color:rgb(207,142,109)">private </span>Region <span style="color:rgb(86,168,245)">createContent</span>() {
        <span style="color:rgb(207,142,109)">var </span>b1 = <span style="color:rgb(207,142,109)">new </span>Button(<span style="color:rgb(106,171,115)">"Click me!"</span>);
        <span style="color:rgb(207,142,109)">var </span>b2 = <span style="color:rgb(207,142,109)">new </span>Button(<span style="color:rgb(106,171,115)">"Click me!"</span>);
        <span style="color:rgb(207,142,109)">var </span>vbox = <span style="color:rgb(207,142,109)">new </span>VBox(b1, b2);
        b1.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
            <span style="color:rgb(199,125,187)">vbox</span>.setVisible(!<span style="color:rgb(199,125,187)">vbox</span>.isVisible());
        });
        b2.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
            <span style="color:rgb(199,125,187)">vbox</span>.setVisible(!<span style="color:rgb(199,125,187)">vbox</span>.isVisible());
        });
        vbox.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
            <span style="color:rgb(199,125,187)">vbox</span>.setVisible(!<span style="color:rgb(199,125,187)">vbox</span>.isVisible());
        });

        <span style="color:rgb(207,142,109)">var </span>stack = <span style="color:rgb(207,142,109)">new </span>StackPane(vbox, <span style="color:rgb(207,142,109)">new </span>StackPane());
        stack.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
            <span style="color:rgb(199,125,187)">vbox</span>.setVisible(!<span style="color:rgb(199,125,187)">vbox</span>.isVisible());
        });
        <span style="color:rgb(207,142,109)">return </span>stack;
    }

    <span style="color:rgb(207,142,109)">public static void </span><span style="color:rgb(86,168,245)">main</span>(String[] args) {
        <span style="font-style:italic">launch</span>();
    }
}</pre>
            </div>
            <p><br>
              It doesn't necessarily have something to do with running
              the visibility change directly in the listener, our
              application does a runLater to change the visibility
              state, still with the same results. To properly debug
              this, you will have to launch the reproducer with a bigger
              stack size like -Xss8m to increase the chance that it
              occurs. Then, you can just set a breakpoint at
              jdk.internal.util.Preconditions:302, and wait for it to
              trigger the OOB eventually.<br>
              <br>
              This problem is currently the biggest JavaFX issue for us
              as it breaks the layout and usually requires a restart to
              fix. <br>
              <br>
              Looking at the bounds calculation code, the list index
              bounds check is very optimistic in that it doesn't check
              any indices and relies on multiple assumtions to hold. So
              if it is very difficult to find the cause, a simple index
              bounds check for the list access would also work fine.</p>
            <p>Best<br>
              Christopher Schnick<br>
            </p>
          </div>
        </blockquote>
      </div>
    </blockquote>
  </div>

</blockquote></div>