The fact that removeAll can actually remove children is very suspicious. To me that would indicate that the same ListView instance is being accessed by multiple threads.
How a stateless page can end up in another thread for another request, is beyond me. Maybe add logging to removeAll for the thread name/ID, the object identity hash code and the number of children before the removal? Or throw an exception if the number of children is not 0 when removeAll is called? Regards, Johan Op donderdag 26 februari 2026 om 16:43 schreef Bas Gooren via users <[email protected]>: > Hi all, > > I have an interesting case, which I will admit I lost some sleep over 😉 > > The ListView API is not stable WRT its model. > > We have not been able to reproduce this, but this is what we get in > production: > > Caused by: java.lang.NullPointerException: Cannot invoke > "java.util.List.get(int)" because the return value of > "org.apache.wicket.markup.html.list.ListView.getModelObject()" is null > at > org.apache.wicket.markup.html.list.ListItemModel.getObject(ListItemModel.java:55) > ~[wicket-core-9.19.0.jar:9.19.0] > at ....SectionsPanel.createSection(SectionsPanel.java:57) ~[-] > at ....SectionsPanel$1.populateItem(SectionsPanel.java:48) ~[-] > at > org.apache.wicket.markup.html.list.ListView.onPopulate(ListView.java:523) > ~[wicket-core-9.19.0.jar:9.19.0] > at > org.apache.wicket.markup.repeater.AbstractRepeater.onBeforeRender(AbstractRepeater.java:124) > ~[wicket-core-9.19.0.jar:9.19.0] > at org.apache.wicket.Component.beforeRender(Component.java:949) > ~[wicket-core-9.19.0.jar:9.19.0] > at > org.apache.wicket.MarkupContainer.onBeforeRenderChildren(MarkupContainer.java:1759) > ~[wicket-core-9.19.0.jar:9.19.0] > > The simplest way to explain this: > > We have a ListView in a stateless page. > The ListView gets a list model mapped from an LDM on the page: > > var parentModel = LoadableDetachableModel.of(() -> > fetchItemFromCacheAndExternalApi()); > … = new ListView(“id”, parentModel.map(Parent::getItems)) { … } > > For a reason unknown at this time, which only happens under high load, this > stateless page is re-rendered. > > On the re-render of the ListView, it walks through its onPopulate method. > > First it calls getViewSize() to check if there are items to render (parent > model is non-null and says: here’s a list of X items) > Reuse items is off, so it calls removeAll => all children are removed and > detached => which also detaches the list model and the page model > It then assumes the “size” is still correct and starts creating and > appending children (ListItem instances). > Once those populate and call their ListItemModel.getObject(), the parent > model reattaches and now returns null => Boom, there’s the NPE. > > The reason that our page model returns non-null and null in quick > succession is spam bots and thus cache issues; An external API we load data > from also seems to misbehave and sometimes return empty results under load. > This particular issue caught me by suprise, as normally a LDM is only > loaded once during render cycle; Here it loads *twice*: for getViewSize() > and then for the first ListItemModel, because it got detached by > ListView#removeAll. > > But more to the point: the ListView proceeds, after calling removeAll(), as > if nothing can have changed. And that is not correct, the ListView model > may have been detached by removing children and thus have a different value > now. > > Should ListView not re-calculate/query getViewSize() after calling > removeAll()? That would remove this edge case? > > Met vriendelijke groet, > Kind regards, > > Bas Gooren > --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
