Is there an existing issue for this?
Are you using the latest BlockUI Version?
Minecraft Version
1.21
BlockUI Version
1.0.211-1.21.1-snapshot
Current Behavior
View's render paths iterate the live children list with an unguarded for-each. If anything structurally modifies that list while a render pass is in flight, the client crashes with ConcurrentModificationException.
Crashed three times with the MineColonies Colony Map open (2026-08-19, then twice on 2026-09-04 two minutes apart). All three reports are identical.
Description: Rendering BO screen
java.util.ConcurrentModificationException: null
at java.base/java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.base/java.util.ArrayList$Itr.next(Unknown Source)
at com.ldtteam.blockui.views.View.drawSelf(View.java:86)
at com.ldtteam.blockui.views.ZoomDragView.drawSelf(ZoomDragView.java:196)
at com.ldtteam.blockui.Pane.draw(Pane.java:357)
at com.ldtteam.blockui.views.View.drawSelf(View.java:90)
at com.ldtteam.blockui.views.BOWindow.drawSelf(BOWindow.java:116)
at com.ldtteam.blockui.Pane.draw(Pane.java:357)
at com.ldtteam.blockui.BOScreen.render(BOScreen.java:106)
at net.minecraft.client.gui.screens.Screen.renderWithTooltip(Screen.java:112)
The crash report's own detail block names the window:
-- BO screen rendering details --
Details:
XML res loc: minecolonies:gui/map/windowcolonymap.xml
Scaling mode (window render type): OVERSIZED_VANILLA
No other mod appears anywhere in the failing stack, it is entirely blockui -> vanilla Screen -> NeoForge ClientHooks.
Analysis
View.java on version/main has seven loops over children, of which exactly one is guarded:
| line |
method |
guarded |
| 86 |
drawSelf |
no |
| 104 |
drawHidden |
no |
| 127 |
drawSelfLast |
no |
| 153 |
findPaneByID |
no |
| 174 |
findPaneByType |
no |
| 190 |
setWindow |
no |
| 260 |
onUpdate |
yes — new ArrayList<>(children) |
Line 86 is the loop in the stack trace:
public void drawSelf(final BOGuiGraphics target, final double mx, final double my)
{
...
for (final Pane child : children) // line 86
{
if (childIsVisible(child)) { child.draw(target, drawX, drawY); }
else { child.drawHidden(); }
}
On the consumer side, MineColonies' WindowColonyMap adds and removes child panes on its dragView as citizens move in and out of the visible area (12+ mutation sites, e.g. dragView.addChild(citizenView) and dragView.removeChild(citizens.get(data))), so in an active colony the child list changes constantly.
What I could not determine: which thread mutates the list during the render pass. WindowColonyMap's mutations all live under onUpdate(), which BlockUI drives from BOScreen#tick(), a different phase from BOScreen#render() and WindowColonyMap has no draw override. So the writer is likely off the render thread. Either way the render pass should not be able to die because of it, which is what the existing guard on line 260 already acknowledges.
Expected Behavior
A child pane being added or removed while a render pass is in flight should not crash the client.
View’s draw loops should be robust to concurrent modification, the same way View#onUpdate already is.
Reproduction Steps
- Join a colony with a good number of citizens moving around.
- Open the MineColonies Colony Map and leave it open.
- Pan/zoom while citizens walk in and out of the visible area.
- The client crashes with the ConcurrentModificationException above.
Intermittent, for me twice within two minutes on one occasion, and once several weeks earlier, which is consistent with a render update race rather than a deterministic code path.
Logs
https://pastebin.com/pUm2ghyE
Anything else?
Environment: MineColonies 1.1.1319-1.21.1, Structurize 1.0.830-1.21.1, NeoForge 21.1.233, Minecraft 1.21.1, Linux amd64, Java 21.0.12 (Temurin), multiplayer, GUI scale 2.
Suggested fix: apply the same defensive copy already used by onUpdate to the three draw methods (drawSelf, drawHidden, drawSelfLast).
If the per-frame allocation is a concern on large views (the colony map holds hundreds of child panes), alternatives would be index based iteration or making children a CopyOnWriteArrayList, reads are per-frame and writes are rare, which suits COW, though it would need a check of the reverse listIterator usage at line 423.
Footer
Viewers
- Add a thumbs-up to the bug report if you are also affected. This helps the bug report become more visible to the team and doesn't clutter the comments.
- Add a comment if you have any insights or background information that isn't already part of the conversation.
Is there an existing issue for this?
Are you using the latest BlockUI Version?
I am also running the latest versions of other mods that are part of my problem.
Minecraft Version
1.21
BlockUI Version
1.0.211-1.21.1-snapshot
Current Behavior
View's render paths iterate the livechildrenlist with an unguarded for-each. If anything structurally modifies that list while a render pass is in flight, the client crashes withConcurrentModificationException.Crashed three times with the MineColonies Colony Map open (2026-08-19, then twice on 2026-09-04 two minutes apart). All three reports are identical.
The crash report's own detail block names the window:
No other mod appears anywhere in the failing stack, it is entirely
blockui-> vanillaScreen-> NeoForgeClientHooks.Analysis
View.javaonversion/mainhas seven loops overchildren, of which exactly one is guarded:drawSelfdrawHiddendrawSelfLastfindPaneByIDfindPaneByTypesetWindowonUpdatenew ArrayList<>(children)Line 86 is the loop in the stack trace:
On the consumer side, MineColonies'
WindowColonyMapadds and removes child panes on itsdragViewas citizens move in and out of the visible area (12+ mutation sites, e.g.dragView.addChild(citizenView)anddragView.removeChild(citizens.get(data))), so in an active colony the child list changes constantly.What I could not determine: which thread mutates the list during the render pass.
WindowColonyMap's mutations all live underonUpdate(), which BlockUI drives fromBOScreen#tick(), a different phase fromBOScreen#render()andWindowColonyMaphas no draw override. So the writer is likely off the render thread. Either way the render pass should not be able to die because of it, which is what the existing guard on line 260 already acknowledges.Expected Behavior
A child pane being added or removed while a render pass is in flight should not crash the client.
View’s draw loops should be robust to concurrent modification, the same way View#onUpdate already is.
Reproduction Steps
Intermittent, for me twice within two minutes on one occasion, and once several weeks earlier, which is consistent with a render update race rather than a deterministic code path.
Logs
https://pastebin.com/pUm2ghyE
Anything else?
Environment: MineColonies 1.1.1319-1.21.1, Structurize 1.0.830-1.21.1, NeoForge 21.1.233, Minecraft 1.21.1, Linux amd64, Java 21.0.12 (Temurin), multiplayer, GUI scale 2.
Suggested fix: apply the same defensive copy already used by onUpdate to the three draw methods (drawSelf, drawHidden, drawSelfLast).
If the per-frame allocation is a concern on large views (the colony map holds hundreds of child panes), alternatives would be index based iteration or making children a CopyOnWriteArrayList, reads are per-frame and writes are rare, which suits COW, though it would need a check of the reverse listIterator usage at line 423.
Footer
Viewers