JavaEE: don’t forget to close your components!
Since JavaEE 7 and even more in JavaEE 8 the JavaEE instances start to get some close() methods. It is true for Bean Validation where the ValidationFactory got a close(), for JSON-B where Jsonb has a close() method, JAX-RS client which has a close() method as well etc...
But why these methods are important and why should you care about them?
Managed vs Unmanaged
First of all there are two cases which are a bit different for you: are the instances managed or not. What does it mean? Do you instantiate the EE component yourself or not. If you get the component injected without defining how then the container is the one responsible of the lifecycle of the instance and you must not care at all.
A common example is with Bean Validation which is integrated with JavaEE and allows you to get injected a Validator or ValidatorFactory through @Resource:
@ApplicationScoped
public class MyValidatorService {
@Resource
private ValidatorFactory factory;
public void validate(final User user) {
// use the factory
}
}
In this case you will not call factory.close() otherwise you will either get an exception from the container saying it is invalid or just prevent further usages of the factory to work.
However, if you instantiate yourself the factory you will have to handle its lifecycle:
@ApplicationScoped
public class MyValidatorService {
private ValidatorFactory factory;
@PostConstruct
private void init() {
factory = Validation.buildDefaultValidatorFactory();
}
public void validate(final User user) {
// use the factory
}
@PreDestroy
private void destroy() {
factory.close();
}
}
Whereas you instantiate yourself or not the instance depends if you write the final application, a reusable library or must be able to run under multiple environments.
Last note is that if you build a library and allow to inject an instance factory you should probably think to provide a destroy/close callback when the library cleans up its state to let the implementation call the corresponding method.
Why close methods are added everywhere?
There are multiple reasons to these close methods addition. The general answer is, indeed, "to clean up some internal state", but if you want some more insight it can be:
- To clean up CDI CreationalContext of the component instances used by the specification for not normal scoped beans (CreationalContext.release() will avoid memory leaks in a lot of cases and more important will ensure pre-destroy callbacks are called for related beans), This is the case for Bean Validation, JSON-B, can be for JAX-RS client, etc...
- To clean up some NIO: in JAX-RS case you can now plug a NIO implementation for the client so if you don't close the client then the client will not release its NIO implementation and pools and will probably leak or fail later,
- To stop some tasks: JBatch has no clear hooks but implementation generally have one which can stop the pending tasks (like batchlets or chunks),
Overall JavaEE design for most specs and lifecycle importance
Due to this release need the specifications need to define the lifecycle of the instances quite precisely. There are two main cases:
- A factory allows to create instances of a component. For instance the ValidatorFactory allows to create a Validator, a EntityManagerFactory allows to create an EntityManager, a JsonBuilderFactory allows to create JsonObjectBuilders and JsonArrayBuilders, etc...
- You directly get a component instance. JsonbBuilder is not useful once you got a Jsonb instance for example.
However, in both cases you can identify a "root" entity, which is generally the one with the close() callback and the one allowing to clean up the state.
The side note here is that it is also the one owning the "caching" if there is any. For instance in bean validation, the factory stores the metadata on the beans to allow the Validator instances to not recompute it each time when possible. Similarly, the Json-P instance can use the pre-allocated buffer caching (memory optimization) for performance reasons.
In all cases, having a root and reusable instance allows some performance optimizations but also, generally, requires to handle properly the lifecycle of this instance to be able to:
- not leak,
- behave as expected - not method missed in CDI model for instance (@PreDestroy),
- be fast and efficient (if you create one instance of this root component per request your application will be slow).
This is why it is important to understand what your components do and are responsible for: use them as they were designed for and not forget to call some important callbacks or not use them in an appropriated scope. Your application will thank you a lot to do that properly.
From the same author:
In the same category: