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
>
>
>

Reply via email to