The request lifecycle according to the servlet specification goes through a chain of filters before finally being executed by a servlet.
This is fairly intuitive when you look at the signature for the doFilter method in the Filter
interface
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
That is, in the filter you have access to the request and response and the chain. The contract is that you, as implementer, should invoke the chain
either before or after the operations you do in the filter, or not at all if it is desired to not continue execution. Calling chain.doFilter(...)
will cause the next filter in the chain of filters with a mapping matching the requested URL to be executed. The final member of the chain is the servlet whose mapping matches the requested URL.
Technically, you can do everything in a filter that you can do in a servlet. You can build your application to do all processing and rendering in a filter and have a blank servlet that does nothing. The main difference is that if there is no servlet mapped against a given URL, the container must respond with a 404 error so there must always be a servlet mapped against any URL you want to serve. You can also only have one servlet mapped against a URL, but you can have any number of filters.