public class GenericServletDispatcher extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
String action = request.getParameter("action");
if (action != null && !action.trim().isEmpty()) {
try {
// Resolve target method with exact signature: HttpServletRequest, HttpServletResponse
Method target = getClass().getMethod(action, HttpServletRequest.class, HttpServletResponse.class);
// Invoke the matched method on current instance
target.invoke(this, request, response);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new ServletException("Failed to dispatch to method: " + action, e);
}
} else {
throw new ServletException("Missing 'action' parameter in request");
}
}
}
Without such a dispatcher, each servlet must manually handle HTTP method routing and parameter decoding:
@WebServlet("/product")
public class ProductController extends HttpServlet {
private final ProductService service = new ProductServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
String op = req.getParameter("action");
switch (op) {
case "fetch":
fetchProducts(req, resp);
break;
case "search":
searchProducts(req, resp);
break;
default:
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unknown action");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
private void fetchProducts(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
List<Product> items = service.findAll();
req.setAttribute("products", items);
req.getRequestDispatcher("/list.jsp").forward(req, resp);
}
private void searchProducts(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String keyword = req.getParameter("q");
List<Product> results = service.findByKeyword(keyword);
req.setAttribute("results", results);
req.getRequestDispatcher("/search.jsp").forward(req, resp);
}
}
The GenericServletDispatcher eliminates boilerplate by centralizing request decoding, encoding setup, and dynamic method dispatch. Subclasses inherit this behavior and expose public handler methods accepting only HttpServletRequest and HttpServletResponse. The this reference inside service() always resolves to the concrete subclass instance — enabling polymorphic method resolution at runtime.
Reflection is used via two core operaitons:
Class.getMethod(String name, Class<?>...): Locates a public, declared method matching name and parameter types — here constrained too(HttpServletRequest, HttpServletResponse).Method.invoke(Object target, Object...): Executes the resolved method on the current servlet instance, passing the typed request/response objects.
Because service(HttpServletRequest, HttpServletResponse) is invoked by the container, the parameters passed into invoke() are already the correct subtypes — no unsafe casting is required. This ensures type safety while preserving flexibility across handler implementations.