Hi,
thanks for the detailed report and for taking the time to attach both log
variants, that made it possible to reproduce locally on the first try.
Short answer: this is not a bug in your code or in the tutorial sources.
You have hit a known interaction between OpenJPA's lazy-load behaviour and
JAX-RS (CXF) marshalling that runs *after* the EJB transaction has
finished. GlassFish + EclipseLink masks the same code's problem because
EclipseLink is more permissive about touching detached/closed-context
proxies.
I reproduced this with apache-tomee-plus-10.1.5 on Temurin 17 using the
exact module you linked. Below is a complete breakdown plus a patch you
can apply to your fork. The diff is also attached as rsvp-fix.patch.
==============================================================================
1) Why /status/all returns 500 in the "no openjpa trace" log
==============================================================================
The relevant lines in your no-trace log are:
WARNING ... org.apache.cxf.jaxrs.provider.JAXBElementProvider.writeTo
<openjpa-4.1.1 fatal user error>
org.apache.openjpa.util.InvalidStateException: The context has been
closed. ...
SEVERE ... Problem with writing the data, class
org.apache.openjpa.kernel.DelegatingResultList, ContentType: application/xml
What is actually happening, step by step:
a) StatusBean#getAllCurrentEvents() executes
em.createNamedQuery(...).getResultList()
OpenJPA returns an *org.apache.openjpa.kernel.DelegatingResultList*.
This is a lazy wrapper, iit does not fully materialise the rows.
b) The @Stateless method returns. The container-managed JTA transaction
commits, and the JPA persistence context is closed.
c) Only *now* does CXF's JAXBElementProvider start iterating the list to
marshal it to XML. While iterating, the DelegatingResultList tries to
pull the next row from the underlying broker, finds the broker is
closed, and throws InvalidStateException("The context has been
closed"). CXF maps that to HTTP 500.
The same pattern bites again one level deeper: JAXB walks the Event
fields (you have @XmlAccessorType(XmlAccessType.FIELD)) and encounters
two LAZY relations:
- @OneToMany(mappedBy = "event") List<Response> responses
- @ManyToMany List<Person> invitees
By spec these default to FetchType.LAZY. They are still unresolved when the
persistence context closes,
so attempting to read them during marshalling fails for exactly the same
reason.
To answer the question you raised: yes, @ManyToOne defaults to
FetchType.EAGER, and OpenJPA does honour that. I verified
it by leaving Event.owner without @XmlTransient in the fix below; owner
serialises correctly into the response payload, because it was already
fully loaded inside the EJB transaction. So the eagerness behaviour you
expect is working; only the LAZY-by-default collections needed
intervention.
The other endpoint, GET /status/{eventId}, works because it returns a
single Event fetched with em.find(...), whose scalar fields and EAGER
relations (owner) are fully loaded synchronously. Same reason
ConfigBean and ResponseBean behave; they do not return lazy result
sets.
2) About the "GlassFish works, TomEE doesn't" difference
The tutorial code is sloppy (IMHO) in a way that EclipseLink tolerates and
OpenJPA does not. The JPA spec leaves "what happens if you touch a lazy
proxy after the persistence context is closed" largely
implementation-defined; providers are free to throw. EclipseLink
typically lets you read whatever state was already loaded and returns
nulls/empties for the rest; OpenJPA is strict and raises
InvalidStateException. Both behaviours are within spec; this is *not*
"OpenJPA not being JPA-compliant". The tutorial just happens to be
written in a way that exercises one of the edges where providers
diverge. The right fix is at the application level (see below), not in
the provider.
3.) The fix
Two minimal application-side changes, no provider workarounds:
(a) In StatusBean.getAllCurrentEvents(), wrap the result in a plain
ArrayList so the data is materialised inside the EJB transaction:
this.allCurrentEvents = new ArrayList<>(
em.createNamedQuery("rsvp.entity.Event.getAllUpcomingEvents",
Event.class).getResultList());
After this, CXF marshals a plain java.util.ArrayList instead of an
OpenJPA DelegatingResultList. No more "context has been closed" on
the outer list.
(b) In Event.java, mark the two LAZY collections @XmlTransient:
@OneToMany(mappedBy = "event")
@XmlTransient
private List<Response> responses;
@ManyToMany
@XmlTransient
protected List<Person> invitees;
These collections are not part of what /status returns
anyway (the endpoint reports events, not invitee/response graphs),
and excluding them from XML/JSON serialisation means JAXB never
tries to walk a LAZY proxy after the persistence context is closed.
Event.owner stays as-is, because @ManyToOne is EAGER by default and
is already loaded by the time the EJB returns.
I have also added the tomee-maven-plugin to rsvp/pom.xml.
<plugin>
<groupId>org.apache.tomee.maven</groupId>
<artifactId>tomee-maven-plugin</artifactId>
<version>10.1.5</version>
<configuration>
<tomeeVersion>10.1.5</tomeeVersion>
<tomeeClassifier>plus</tomeeClassifier>
<context>rsvp</context>
</configuration>
</plugin>
That makes it trivial to reproduce / regression-test the example locally:
mvn package
mvn tomee:run
and then GET http://localhost:8080/rsvp/webapi/status/all .
Hope this helps - and good luck with the rest of the tutorial!
Gruß
Richard
> Am 17.05.2026 um 03:53 schrieb Gabriel Alejandro Soto Rivera
> <[email protected]>:
>
> Warm regards to everyone, and thank you very much in advance.
>
> I’m just starting to learn Jakarta EE and Tomee, so I’m not sure if this is
> the right
> place to ask for help with my problem. But I think it is, because it’s
> related to Tomee.
>
> I’m learning about Jakarta EE using the tutorial on their website:
> Jakarta EE Tutorial I’ve just modified the POMs so that they don’t use
> GlassFish
> by default, but instead run on the Tomee server I’ve installed locally; and
> change the
> persistence.xml to not use eclipse as persistence provider. And, so far, the
> examples
> have been working correctly.
>
> I am using Tomee 10.0.5 with their default configurations, only and a user in
> tomcat-users
> to access to the html manager. Then, to test if is the example that is not
> working or is Tomee
> I installed GlassFish 7 and the example works well. But I want to make it
> work with Tomee.
>
> But I got stuck on the example in ‘Building RESTful Web Services with Jakarta
> REST’
> The rsvp Example Application. Making some testing I discover that the
> endpoints of
> ResponseBean.java works well; and ConfigBean.java make their job. But the
> problem
> comes from executing the endpoints of StatusBean.java
> However, when trying to debug, I get confusing logs depending on the
> configuration.
> With the default configuration, the log indicates issues related to
> ‘org.apach.cxf.jaxrs.provider.JAXBElementProvider’.
> However, when adding
> <property name="openjpa.Log" value="DefaultLevel=TRACE, Runtime=TRACE,
> Tool=TRACE"/>
> to persistence.xml as the log suggests to obtain more information, the
> application fails
> to deploy. It throws errors relating to
> ‘jdk.internal.reflect.NativeMethodAccessorIpml.invoke’.
> I have the tutorial code with the changes that i make to work with Tomee in
> my github:
> https://github.com/Sxtormulo/jakartaee-examples/tree/main/tutorial/jaxrs/rsvp.
> These are the errors I get when I try to run the example with and without the
> openjpa.Log Trace
> property in persistence.xml.
> As I’m new to this, I’m not sure if it’s a configuration error, an app error
> or a bug.
> If it is a bug, I’ll open an issue on GitHub to report it.
> Thank you very much in advance for your help.