Yes, we're recursively calling ourselves as we walk through the array of filters.

There really isn't a nice way to use some sort of idiom like:

  while (filter()) {
  }

because each filter in the chain gets to decide on its own whether or not we are allowed to call the next filter, or even call the servlet.  If the filter doesn't call our this.doFilter we can't continue down the chain.  If it does call us again, we have to keep going.

The only other alternative pattern would be to construct a new FilterChain instance on each filter invocation, e.g.:

  private class DoFilter implements FilterChain {
    private final int next;

    DoFilter(int idx) {
      next = idx;
    }

    public void doFilter(ServletRequest req, ServletResponse rsp) {
      if (next < filters.length)
        filters[next].doFilter(req, rsp, new DoFilter(next + 1));
      else
        servlet.service(req,rsp);
    }
  }

But this has the penalty of causing an object allocation per filter invocation and still has the same structure.

I actually looked around a bit before implementing this code.  Both Jetty and Guice Servlet implement this same style of coding for filter dispatch, to reduce the number of temporary objects they have to create during request dispatching.