May 24, 2015

Java EE 7 Bean scopes compared (part 2 of 2)


In this article, I take a closer look at the various lifecycle “scopes” specified by Java EE 7’s Bean definitions. I created a simple demo web application which provides a  comparison of JSF, CDI, EJB and “Dependency Injection for Java” Bean scopes, which you should check out here first. Here come my conclusions.

Lessons learnt!

In this section, I will present you my findings as I played with the demo application myself, and their consequences for proper usage of specific bean scopes and types.

For comparison purposes, I grouped cross-framework “similar” scope definitions in rows in the demo application. I will now go through these rows and explain my findings. I highly encourage you to try this out yourself and draw your own conclusions as well.

No scope / dependant scope

Similar scope definitions are provided by:
  • JSF’s @javax.faces.bean.NoneScoped
  • CDI’s @javax.enterprise.context.Dependent
  • EJB’s @javax.ejb.Stateful
Although conceptually different, these three scope types behave quite similar; their lifespan is typically “less than request scoped”, or it is bound to the lifespan of a referencing “parent” bean, if present. When used directly in the JSF EL, a new bean is created for every reference in an EL expression.

Note than of these three scopes, CDI’s @Dependent is the only one which is destroyed as soon as the parent is, e.g. after usage in a JSF EL expression.

EJB @Stateful beans, on the other hand, are individual, long-living instances of an EJB eligible for holding state. They must be explicitly destroyed.

JSF’s @NoneScope really is just a helper scope, making the bean eligible for dependency injection into any other JSF bean independent of that bean’s type.

Request scope

Equivalent scope definitions are provided by:
  • JSF’s @javax.faces.bean.RequestScoped
  • CDI’s @javax.enterprise.context.RequestScoped
The lifespan of these beans is bound to the HTTP request’s lifespan. Regardless of whether a new request is invoked with GET or POST, a request scoped bean will be destroyed / recreated with every new HTTP request.

These two scopes thus provide a means for truly stateless JSF navigation, as e.g. used in a RESTful architecture. There are however some known pitfalls when used with JSF as this framework is optimized for at least some state being held on the server side.

Flash scope

This scope is exclusively provided by:
  • JSF’s javax.faces.context.Flash
Flash scope works quite differently from all the other scopes discussed here. It doesn’t work by annotating bean classes; rather, it provides a Map-like structure which you can access from JSF EL (flash) or by dependency injection (Flash), in order to explicitly store and retrieve objects.

Flash is literally the next higher scope after request as any object put into flash scope will have a lifespan of not one, but two HTTP requests. This actually closes a gap coming up quite frequently in everyday JSF development, and to quickly put an object in flow scope should really be seen as a practical workaround.

It is however bad practice to abuse this facility for use cases where higher scopes would be appropriate. Also note that flash scope has been notorious for buggy behavior in earlier Oracle Mojarra 2.1 and 2.2 releases. Make sure to upgrade to the latest version in order to avoid trouble!

View scope

(Theoretically) equivalent scope definitions are provided by:
JSF 2.2 has here introduced some serious confusion: Prior to that version, the only view scope available in the Java EE 7 stack was JSF’s own @javax.faces.bean.ViewScoped annotation, available for JSF @ManagedBean beans only.

But with JSF 2.2, a new scope has been introduced, namely @javax.faces.view.ViewScoped (mind the package name!) which is actually a port of the original view scope for use not in JSF beans, but in CDI beans. Yes, that’s right. The JSF specification defines a CDI scope type for exclusive use with CDI beans. This is perfectly reasonable as CDI exposes a means to implement custom scopes with the @NormalScope base annotation; this is what JSF 2.2 did. The real world motivation behind this is that JSF’s @ManagedBeans are thought to become @Deprecated in the near future and to eventually get removed altogether from the Java EE stack in favor of CDI beans. With that upgrade, the view scope, formerly not available for CDI beans, would still be preserved. I thus placed the “new” view scope in the list of CDI scopes.

The view scope’s lifespan essentially lies between request scope and session scope: It will survive postbacks, e.g. POST requests to the origin page.

It will though never outlive the session as it is bound to it: If the session is destroyed, so are all view scopes bound to it.

However, with any GET request as well as with POST requests with navigation to a different page, a new view scope will be opened. This scope thus is really built for dealing with AJAX calls.

As the demo application shows, the situation is actually even quite worse: With a POST to a different page, at least the previous view scope is destroyed. But with GET, this is not the case, which is a conceptual problem: The server cannot know whether e.g. a new browser tab has been opened for the GET request or whether the origin page has been changed to a new URL. It must assume that the former may be the case, and if so, it will by design try to retain parallel scopes rather than destroying them. Thus, with each GET request, a new view scope will be created, but the previous one will not be destroyed. This is potentially a huge memory leak.

Empirical tests with the demo application however show a peculiar behavior: Sending GET requests will show the described leaky behavior, but only up to 25 times! After 25 view scopes have been opened with one session, with every new GET request, an active view scope will be destroyed, stabilizing the number of active view scopes per session to 25. What’s even more curious, this only appplies to JSF’s original @javax.faces.bean.ViewScoped implementation, not to CDI’s @javax.faces.view.ViewScoped. I don’t understand where the magical number of 25 comes from. I don’t know whether this behavior is a bug or not; I have no explanation for it.

For the moment, I must assume that both @ViewScoped implementations are either leaky or buggy, or both. I’d recommend not to use either of them and use one of the cleaner higher scopes instead.

As per styrand’s comment, I later tried out the @org.omnifaces.cdi.ViewScoped implementation provided by the OmniFaces JSF util library, which is meant to be used as a backport for the ViewScoped CDI extension for use with JSF 2.0 / 2.1. Curiously, this implementation shows yet another behavior: It seems to be the only one which actually respects the com.sun.faces.numberOfLogicalViews web.xml context parameter, but only when set explicitly; instead of taking the officially documented default number of 15, it seems to default to 20. I am still inclined to consider this this only proper ViewScoped implementation, and to recommend using it with a numberOfLogicalViews parameter set as small as possible.

As view scope is bound to the session, the same restrictions and memory leak possibilities apply. Unlike the session, a view does not span over multiple browser windows or tabs.

Conversation scope

This scope is exclusively provided by:
  • CDI’s @javax.enterprise.context.ConversationScoped
Conversation scope is a flexible scope with a lifespan lying between request scope and session scope. A conversation can be programmatically opened and closed. The scope will be opened at the beginning of the conversation, and is destroyed as soon as it ends. However, as long as no conversation is opened, conversation scope’s lifespan is bound to an HTTP requests, and the scope is destroyed and recreated on each request (as a so-called “transient conversation scope”).

As long as the conversation is open, no request (GET or POST) can end the conversation.

However, as the conversation is bound to the session, it is destroyed as soon as the session is.

You can open multiple conversations per session; however, you cannot open a new conversation while being in a conversation.

You need to keep the conversion id as a request parameter for every subsequent HTTP request to keep track of the current conversation. If you loose the parameter, the old conversation will still be kept open, but for the new request, a new (transient) conversation is opened.

Empirical tests with the demo application confirm that this is a potential memory leak. By simple “forgetting” the conversation id with an HTTP request (thus opening a new transient conversation) and subsequent opening of a new long-running conversation, and repeating these steps over and over again, a possibly infinite number of conversations can be opened. Luckily, you as the developer can, and must, make sure that this cannot be exploited by adding appropriate checks on any method which spawns a new long-running conversation.

As conversation scope is bound to the session, the same restrictions and memory leak possibilities apply. Unlike the session, a conversation does not span over multiple browser windows or tabs.

Flow scope

This scope is exclusively provided by:
  • JSF’s / CDI’s @javax.faces.flow.FlowScoped
Now, the confusion is perfect! Yes, just as @javax.faces.view.ViewScoped, this is another CDI bean scope provided by JSF 2.2. And of course, it is completely unrelated to the somewhat similar sounding “Flash scope”, which is a rather recent addition to JSF as well. In the demo application, I placed this flow scope in the list of CDI scopes as well.

Flow scope works quite different from any other scopes discussed here as it comes with very strict requirements for its application. Flow scope can only be applied to XHTML pages which adhere to an explicitly provided XML- or Java-based configuration, enforcing e.g. placing the XHTML pages in a dedicated folder.

Thus, the typical use case of flow scope really is confined to true “page flows” and “multi-page wizards”. Even though the flow accepts logic for dynamic page navigation, it is clearly not as dynamic as are other scopes discussed here.

Also, at its core, the flow facility isn’t actually a CDI scope; the scope really is just an addition. A flow primarily consists of a Flow instance marked as a @FlowDefinition and which defines page navigation which can then be invoked from the UI by using implicit navigation outcomes. Depending on the complexity of the flow’s logic, you may or may not define a number of beans annotated with the respective @FlowScoped("myFlow") directive; for more simple cases, the implicit JSF EL object flowScope, which is backed by a Map, may suffice.

Note that as with the conversation scope, the user can leave the flow scope by manipulating the URL, potentially leaving back “lost” flow scoped beans. This, in my opinion, is a potential memory leak.

If you consider using flow scope, note that the flow facility as such is tightly bound to JSF, further increasing dependency on that UI framework for your application.

A flow does not span over multiple browser windows, but it does span over multiple tabs.

Session scope

Equivalent scope definitions are provided by:
  • JSF’s @javax.faces.bean.SessionScoped
  • CDI’s @javax.enterprise.context.SessionScoped
The session scope is the classic solution for any scope lifespan higher than request. And it typically is a bad solution as its lifespan is really too long for most use cases. There isn’t much more explanation left. The scope’s lifespan is bound to the session which can be either explicitly destroyed on the server side by calling ExternalContext#invalidateSession() or which will be destroyed after user idle time defined by web.xml’s <session-config><session-timeout> parameter. A session spans over multiple browser tabs and windows.

However, recognizing a user’s session on the server side relies on the HTTP request sending the JSESSIONID cookie. After deletion of this cookie on the client side, any new HTTP request will spawn a new session whereas the previous session will stay alive until session timeout is reached. This is a potential memory leak! (And there is basically nothing you can do against it in your app, and you have to find other ways to secure your server-side infrastructure.)

Misusage, including over-usage of the session to hold information longer than for one request is considered poor practice anyways. It typically leads to cluttered state information held by a multitude of beans, proving the central wisdom of functional programming, that maintaining mutable state is hard and error-prone. Actually, you should really perceive the session scope as your last resort. In most cases you can even go away with plain request scope, transferring state information through request parameters. When applied in perfection, this is what a true RESTful page navigation amounts to, as I just described it in the previous blog post series.

For other cases, you may want to consider using flash scope, conversation scope or flow scope.

Stateless scope

This scope is exclusively provided by:
  • EJB’s @javax.ejb.Stateless
Although in the demo application, EJB’s @Stateless and @Singleton show the same behavior, their implementation and their use case is actually very different when it comes to concurrent access:

When accessed concurrently, the server may decide whether to re-use an existing, pooled instance of the bean or whether to create and yield a new instance and add it to the pool.

This has important consequences when dealing with state as you just cannot rely on any state held by a stateless bean: upon the next invocation, the bean (and thus its state) may be completely different. Thus, a @Stateless bean should, as its name suggests, really be used exclusively if its doesn’t hold any state at all. This is typically the case for a service-oriented implementation.

If you instead search for an EJB holding a global state, go for @Singleton.

Global / singleton scope

Equivalent scope definitions are provided by:
  • JSF’s @javax.faces.bean.ApplicationScoped
  • CDI’s @javax.enterprise.context.ApplicationScoped
  • EJB’s @javax.ejb.Singleton
  • Dependency Injection for Java’s @javax.inject.Singleton
As their names suggest, all these bean scopes / bean types exist as singleton instances exactly once during application lifetime. Their lifespan begins when the application starts up, and ends when the application or the application server is shut down.

As such, they can be used to hold global state or as stateless implementations. If your use case requires the latter, and going for an EJB bean is an option, you should however consider creating a @Stateless EJB bean.

It’s very important to realize that you, as the developer, are in charge of dealing with concurrent access to that singleton instance. You must pay close attention to access synchronization in order to prevent race conditions.

You will typically use singleton beans for solutions to rather technical problems only, and use much more restricted scopes in everyday programming. On the other hand, singleton scoped beans are a great replacement for any statically implemented functionality (e.g. factories). As they can be easily mocked, they greatly reduce component interdependency and increase testability.

Conclusion

Creating this demo application and playing around with bean scopes revealed some interesting, and even some fairly shocking properties of various Java EE 7 Bean scope definitions.

As a final conclusion, I am now even more certain that one should stick with the shortest-living scope lifespan in order to avoid memory leaks:
  • Use “no scope” only where it technically makes sense.
  • Use request scoped beans whenever possible.
  • Use the flash scope if you just need to carry over information from one request to the next.
  • The view scope is leaky by design, and potentially buggy as well. I would thus advice against using it. I don’t have enough experience with the OmniFaces implementation yet, but maybe it’s a true alternative. However, you then have to be aware that you’re leaving the Java EE standard.
  • For page flows, I would go for the @ConversationScoped solution, and make sure that beginning / ending a conversation is well controlled, destroying orphan conversations prior to adding new ones. Note that this solution still suffers from the underlying potential leaky session concept.
  • As I didn’t found a way to recover “lost” flow scoped objects / beans, I wouldn’t use that scope and use @ConversationScoped instead.
  • The session is potentially leaky, and so are all the scopes built on top of it. As you cannot prevent deletion of the JSESSIONID cookie, you have to apply other methods to save your server-side infrastructure from session flooding. Keeping the session as short-living as possible certainly helps as well.
  • Use singleton beans when it technically makes sense.
Please let me know in the comments section below whether you found this demo application useful and if you came to the same conclusion as I did. Please alert me as well if you found an error in this blog post or the demo application.

Updated June 21, 2015: Included OmniFaces' @ViewScoped implementation


Pages: 1 2

2 comments:

  1. Thanks for your very interesting article. I have some questions about the ViewScoped :
    - have you tested the Omnifaces implementation ?
    - is the number of active view scopes per session a context param of jsf ?

    ReplyDelete
    Replies
    1. Thank you for your comment. In fact, I considered including the OmniFaces implementation when I wrote the article, but I finally decided to stick with the standards only. As you challenged me to do so, I gave it a try, and I made some interesting observations: Apparently, the OmniFaces implementation of ViewScoped respects the com.sun.faces.numberOfLogicalViews web.xml context parameter which the two other implementations (JSF / CDI) seem to ignore. I still don’t know where the magic number of 25 sessions per view comes from, but I am now inclined to recommend OmniFaces’ implementation exclusively when there is a ViewScoped requirement. I have updated the application accordingly. I would be very interested in hearing any further discoveries you make on this matter.

      Delete