Hi Johan, Thank you for responding.
Well removeAll removing children is actually logical (in a ListView), since it is supposed to be “refreshing” its contents 🙂 My stateless page is not ending up in *another* thread - but it *is* being rendered *more than once* in the same thread. But that is something that can only be specific to this web app, since I have never seen that anywhere else. So the main issue here is that ListView#onPopulate() detaches its own model if there are children to remove, which means that getViewSize() can return a different size. Thus it is not “stable” with regard to its own invariants, I think… I have now worked around this by having my page-level LDM bound to the request cycle; So load() doesn’t actually lead to fetching state more than once in a single request cycle. But I must say that feels like a crutch and not very wicket-y, and I say this after a *long* time working with Wicket. I mean, look at the descriptions in the user guide ( https://nightlies.apache.org/wicket/guide/10.x/single.html#_detachable_models ): "This interface provides a method called detach() which is invoked by Wicket *at the end of web request processing* when data model is no more needed but before serialization occurs. Overriding this method we can clean any reference to data object keeping just the information needed to retrieve it later (for example the id of the table row where our data are stored). In this way we can avoid the serialization of the object wrapped into the model overcoming both the problem with non-serializable objects and the one with large data objects. Since IModel inherits from IDetachable, every model of Wicket is “detachable”, although not all of them implement a detaching policy (like the Model class). Usually detaching operations are strictly dependent on the persistence technology adopted for model objects (like a relational db, a NoSQL db, a queue, etc), so it’s not unusual to write a custom detachable model suited for the persistence technology chosen for a given project. To ease this task Wicket provides abstract model LoadableDetachableModel. This class internally holds a transient reference to a model object which is initialized the first time getObject() is called to process a request. The concrete data loading is delegated to abstract method T load(). The reference to a model object is automatically set to null *at the end of the request by the detach()* method.” This is how I have always worked with LDMs. Of course I know that at any time detach *can* be called. But to discover that ListView and other repeaters actually do this on *every* re-render when they already have children that need to be cleaned up, was kind of a shocker. Yeah, looking at the source it seems logical and harmless. But if the model passed to the repeater wraps (is connected to) a “heavy” LDM like a complex DB query, than the query will be run *multiple times* in the same render. And that does not make much sense to me. Met vriendelijke groet, Kind regards, Bas Gooren Op 27 feb 2026, 10:03:07 schreef Johan Stuyts <[email protected]>: > 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 > > >
