Frequently there is confusion about passivation of contextual instances with CDI, especially when non-passivating scopes such as request scoped are injected in passivatable once.
The specification defines passivation (serialization) capabilities, allowing the container to free memory by transferring idle objects to secondary storage when required.
The temporary transfer of the state of an idle object held in memory to some form of secondary storage is called passivation.
The transfer of the passivated state back into memory is called activation.
CDI-Spec 1.2 – Chapter 6.6.1
CDI’s session and conversation scopes are the only build-in passivation capable scopes. Request and application scopes are not passivating capable. If a bean is defined with a passivating scope, it and all applied interceptors and decorators must be serializable (see CDI 1.2 Specification – 6.6.1. Passivation capable beans).
Passivation of Normal Scoped injections
When working with passivation capable scopes such as @SessionScoped, it is still possible to inject references to non-passivating scopes such as @ApplicationScoped. Application scoped beans are not passivation capable, however the injected dependencies are.
[java]
@SessionScoped
public class MySession implements Serializable {
@Inject
private MyAppData myAppData;
…
}
@ApplicationScoped
public class MyAppData {
…
}
[/java]
Even so the application scoped bean MyAppData defines a non-passivation capable scope and is not serializable, whereas the client-proxy CDI injects is. When the container passivates the session scoped instance, the injected client-proxies are serialized along with it.
When a method is invoked on the client-proxy, a contextual lookup is performed via the BeanManager, which manages creation and access to all contextual beans.
Beware of transient
Most static code analysis tools, such as FindBugs, will raise false warnings when encountering injections of non-serializable managed beans into passivation capable beans. If myAppScoped was marked transient, as suggested…
[java]
public class MySession implents Serializable {
@Inject
private transient NonSerializableAppScope myAppScoped;
…
}
[/java]
…the client proxy will not be serialized nor re-injected during deserialization and remains null. This will cause a NullPointerException on the next invocation.
Dependant beans
Relaying on the client-proxy works for all normal scopes. Pseudo scopes beans are injected as direct references. So when a pseudo scoped bean such as @Dependent, is injected into a bean with a passivating scope it will need to be serializable or transient.
[java]
public class MySession implements Serializable {
@Inject
private MyAppScope myAppScope;
@Inject
private MyDependent myDependent;
…
}
@Dependent
public class MyDependent implements Serializable {
…
}
[/java]
If MyDependent is not Serializable then the myDependent will have to be marked transient. Making the dependent beans inject point transient is not advisable since it will require manual handling during deserialization.
CDI performs passivation validations on all beans defining passivating scopes prior to deployment.
For every bean which declares a passivating scope, the container must validate that the bean truly is passivation capable and that, in addition, its dependencies are passivation capable.
If a managed bean which declares a passivating scope, a stateful session bean which declares a passivating scope, or a built-in bean:
- is not passivation capable,
- has an injection point that is not passivation capable,
- has an interceptor or decorator that is not passivation capable
- has an interceptor or decorator with an injection point that is not passivation capable
then the container automatically detects the problem and treats it as a deployment problem.
CDI-Spec 1.2, Chapter 6.6.5
When injecting a non-serializable dependent scoped bean into a bean with a passivating scope…
[java]
@SessionScoped
public class MySession implements Serializable {
…
@Inject
private MyUnserializable mu;
…
}
@Dependent
public class MyUnserializable {
…
}
[/java]
…the container detects this at start-up.
The specification does not define as to how the implementation should handle the deployment problem;
Weld throws a UnserializableDependencyException,
[java]
Caused by: org.jboss.weld.exceptions.UnserializableDependencyException:
WELD-001413: The bean Managed Bean [class com.knitelius.MySession] with qualifiers [@Any @Default] declares a passivating scope but has a non-passivation-capable dependency Managed Bean [class com.knitelius.MyUnserializable] with qualifiers [@Any @Default]
at org.jboss.weld.bootstrap.Validator.validateInjectionPointPassivationCapable(Validator.java:479)
at org.jboss.weld.bootstrap.Validator.validateInjectionPointForDeploymentProblems(Validator.java:397)
[/java]
whilst OpenWebBeans throws a WebBeansConfigurationException.
[java]
Caused by: org.apache.webbeans.exception.WebBeansConfigurationException: Passivation capable beans must satisfy passivation capable dependencies. Bean : 1113977599,Name:mySession,WebBeans Type:MANAGED,API Types:[java.io.Serializable,java.lang.Object,com.knitelius.MySession],Qualifiers:[javax.enterprise.inject.Any,javax.enterprise.inject.Default,javax.inject.Named] does not satisfy.
at org.apache.webbeans.component.AbstractOwbBean.validatePassivationDependencies(AbstractOwbBean.java:702)
at org.apache.webbeans.component.AbstractInjectionTargetBean.validatePassivationDependencies(AbstractInjectionTargetBean.java:596)
at org.apache.webbeans.config.BeansDeployer.checkPassivationScope(BeansDeployer.java:730)
at org.apache.webbeans.config.BeansDeployer.validate(BeansDeployer.java:382)
[/java]
This is usually a sign of bad design and therefore either the unserializable bean should be:
- assigned a non-passivating scope such as @RequestScoped,
- its part of the contextual domain model and should be seralizeable
- or its a utility class which should only be used as a method local variable and not as a member.
For the other 0.01% of cases it is possible to implement java.io.Serializable readObject to perform custom handling during deseralization .
[java]
…
@Inject
private BeanManager beanManager;
@Inject
private transient MyUnserializable mu;
private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
Bean<MyUnserializable> bean = (Bean<MyUnserializable>) beanManager.resolve(beanManager.getBeans(MyUnserializable.class));
this.mu = (MyUnserializable) beanManager.getReference(bean, bean.getBeanClass(), beanManager.createCreationalContext(bean))
}
…
[/java]
The a new instance of dependent instance of MyUnserializable can be obtained during deseralization from the BeanManager.
It is important to note that the CDI container, as per specification, is only required to validate injection points. All other members are ignored by the container.
[java]
@SessionScoped
public class MySession implements Serializable {
…
private UnserializableObject uso = new UnserializableObject();
…
}
[/java]
Even though MySession passes the CDI deployment validation, it will fail serialization at runtime due to its non-transient unserializable member causing a java.io.NotSerializableException. All unmanaged members have to be either serializable or transient.
Constructor and Initializer method parameter injection
Non-serializable beans that are only required for bean initialization can be injected into constructor or intializer methods with the @TransientReference annotation.
[java]
@SessionScoped
public class MySession implements Serializable {
@Inject
public MySession(@TransientReference NonSerializableDependent nsd) {
…
}
…
@Inject
public void initalize(@TransientReference NonSerializableDependent nsd) {
…
}
…
}
[/java]
These @TransientReference dependent scoped beans will be be destroyed at the end of the method invocation.
…
- all @Dependent scoped contextual instances injected into method or constructor parameters that are annotated with @TransientReference are destroyed when the invocation completes,
Wrap up
- Normal-scoped beans ARE injected as serializable client-proxies, these DO NOT have to be serializable, unless defining a passivating scope themselves, and should NEVER be transient.
- Pseudo Scoped beans ARE validated by the CDI container at deployment, but HAVE to be serializable or transient if injected into a a bean declaring a passivating scope.
- Member variables ARE NOT validated by the CDI container, they HAVE to be serializable or transient if they are the member of a bean declaring a passivating scope.
Excellent article, thanks for writing it down!
Thanks, nice tips. Very clear explanation
Interesante artículo.
Gracias por el aporte.
When a non serializable field is injected as transient in a passivation capable bean, does or will cdi offer a way to re-inject after activation (such as @PostActivate for ejb stateful beans)?
Or is readObject going to be the only way?
No – the only way would be to utilize readObject method.
What about injecting a resource produced and instantiated by a Producer using the @Produces annotation? This is a common practice to inject instance specific resources like a database connection that is not really persistable, or a HttpServletContext.
thanks welcoming read
Thanks as well,
this was really helpful