I have written simple application with container-managed security. The problem is when I log in and open another page on which I logout, then I come back to first page and I click on any link etc or refresh page I get this exception. I guess it's normal (or maybe not:)) because I logged out and session is destroyed. What should I do to redirect user to for example index.xhtml or login.xhtml and save him from seeing that error page/message?
In other words how can I automatically redirect other pages to index/login page after I log out?
Here it is:
javax.faces.application.ViewExpiredException: viewId:/index.xhtml - View /index.xhtml could not be restored.
at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:212)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:110)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:312)
at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1523)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:343)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:215)
at filter.HttpHttpsFilter.doFilter(HttpHttpsFilter.java:66)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:215)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:277)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:188)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:641)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:97)
at com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:85)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:185)
at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:325)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:226)
at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:165)
at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:791)
at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:693)
at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:954)
at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:170)
at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:135)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:102)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:88)
at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:76)
at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:53)
at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57)
at com.sun.grizzly.ContextTask.run(ContextTask.java:69)
at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:330)
at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:309)
at java.lang.Thread.run(Thread.java:619)
Best Answer
Introduction
The
ViewExpiredException
will be thrown whenever thejavax.faces.STATE_SAVING_METHOD
is set toserver
(default) and the enduser sends a HTTP POST request on a view via<h:form>
with<h:commandLink>
,<h:commandButton>
or<f:ajax>
, while the associated view state isn't available in the session anymore.The view state is identified as value of a hidden input field
javax.faces.ViewState
of the<h:form>
. With the state saving method set toserver
, this contains only the view state ID which references a serialized view state in the session. So, when the session is expired or absent for one of the following reasons ...HttpSession#invalidate()
is called in serverSameSite=None
is missing on session cookie (and thus e.g. Chrome won't send them along when a 3rd party site (e.g. payment) navigates back to your site via a callback URL)... then the serialized view state is not available anymore in the session and the enduser will get this exception. To understand the working of the session, see also How do servlets work? Instantiation, sessions, shared variables and multithreading.
There is also a limit on the amount of views JSF will store in the session. When the limit is hit, then the least recently used view will be expired. See also com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews.
With the state saving method set to
client
, thejavax.faces.ViewState
hidden input field contains instead the whole serialized view state, so the enduser won't get aViewExpiredException
when the session expires. It can however still happen on a cluster environment ("ERROR: MAC did not verify" is symptomatic) and/or when there's a implementation-specific timeout on the client side state configured and/or when server re-generates the AES key during restart, see also Getting ViewExpiredException in clustered environment while state saving method is set to client and user session is valid how to solve it.Regardless of the solution, make sure you do not use
enableRestoreView11Compatibility
. it does not at all restore the original view state. It basically recreates the view and all associated view scoped beans from scratch and hereby thus losing all of original data (state). As the application will behave in a confusing way ("Hey, where are my input values..??"), this is very bad for user experience. Better use stateless views or<o:enableRestorableView>
instead so you can manage it on a specific view only instead of on all views.As to the why JSF needs to save view state, head to this answer: Why JSF saves the state of UI components on server?
Avoiding ViewExpiredException on page navigation
In order to avoid
ViewExpiredException
when e.g. navigating back after logout when the state saving is set toserver
, only redirecting the POST request after logout is not sufficient. You also need to instruct the browser to not cache the dynamic JSF pages, otherwise the browser may show them from the cache instead of requesting a fresh one from the server when you send a GET request on it (e.g. by back button).The
javax.faces.ViewState
hidden field of the cached page may contain a view state ID value which is not valid anymore in the current session. If you're (ab)using POST (command links/buttons) instead of GET (regular links/buttons) for page-to-page navigation, and click such a command link/button on the cached page, then this will in turn fail with aViewExpiredException
.To fire a redirect after logout in JSF 2.0, either add
<redirect />
to the<navigation-case>
in question (if any), or add?faces-redirect=true
to theoutcome
value.or
To instruct the browser to not cache the dynamic JSF pages, create a
Filter
which is mapped on the servlet name of theFacesServlet
and adds the needed response headers to disable the browser cache. E.g.Avoiding ViewExpiredException on page refresh
In order to avoid
ViewExpiredException
when refreshing the current page when the state saving is set toserver
, you not only need to make sure you are performing page-to-page navigation exclusively by GET (regular links/buttons), but you also need to make sure that you are exclusively using ajax to submit the forms. If you're submitting the form synchronously (non-ajax) anyway, then you'd best either make the view stateless (see later section), or to send a redirect after POST (see previous section).Having a
ViewExpiredException
on page refresh is in default configuration a very rare case. It can only happen when the limit on the amount of views JSF will store in the session is hit. So, it will only happen when you've manually set that limit way too low, or that you're continuously creating new views in the "background" (e.g. by a badly implemented ajax poll in the same page or by a badly implemented 404 error page on broken images of the same page). See also com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews for detail on that limit. Another cause is having duplicate JSF libraries in runtime classpath conflicting each other. The correct procedure to install JSF is outlined in our JSF wiki page.Handling ViewExpiredException
When you want to handle an unavoidable
ViewExpiredException
after a POST action on an arbitrary page which was already opened in some browser tab/window while you're logged out in another tab/window, then you'd like to specify anerror-page
for that inweb.xml
which goes to a "Your session is timed out" page. E.g.Use if necessary a meta refresh header in the error page in case you intend to actually redirect further to home or login page.
(the
0
incontent
represents the amount of seconds before redirect,0
thus means "redirect immediately", you can use e.g.3
to let the browser wait 3 seconds with the redirect)Note that handling exceptions during ajax requests requires a special
ExceptionHandler
. See also Session timeout and ViewExpiredException handling on JSF/PrimeFaces ajax request. You can find a live example at OmniFacesFullAjaxExceptionHandler
showcase page (this also covers non-ajax requests).Also note that your "general" error page should be mapped on
<error-code>
of500
instead of an<exception-type>
of e.g.java.lang.Exception
orjava.lang.Throwable
, otherwise all exceptions wrapped inServletException
such asViewExpiredException
would still end up in the general error page. See also ViewExpiredException shown in java.lang.Throwable error-page in web.xml.Stateless views
A completely different alternative is to run JSF views in stateless mode. This way nothing of JSF state will be saved and the views will never expire, but just be rebuilt from scratch on every request. You can turn on stateless views by setting the
transient
attribute of<f:view>
totrue
:This way the
javax.faces.ViewState
hidden field will get a fixed value of"stateless"
in Mojarra (have not checked MyFaces at this point). Note that this feature was introduced in Mojarra 2.1.19 and 2.2.0 and is not available in older versions.The consequence is that you cannot use view scoped beans anymore. They will now behave like request scoped beans. One of the disadvantages is that you have to track the state yourself by fiddling with hidden inputs and/or loose request parameters. Mainly those forms with input fields with
rendered
,readonly
ordisabled
attributes which are controlled by ajax events will be affected.Note that the
<f:view>
does not necessarily need to be unique throughout the view and/or reside in the master template only. It's also completely legit to redeclare and nest it in a template client. It basically "extends" the parent<f:view>
then. E.g. in master template:and in template client:
You can even wrap the
<f:view>
in a<c:if>
to make it conditional. Note that it would apply on the entire view, not only on the nested contents, such as the<h:form>
in above example.See also
Unrelated to the concrete problem, using HTTP POST for pure page-to-page navigation isn't very user/SEO friendly. In JSF 2.0 you should really prefer
<h:link>
or<h:button>
over the<h:commandXxx>
ones for plain vanilla page-to-page navigation.So instead of e.g.
better do
See also