← Back to list

Olympus Router (Supports JEE7+)

Java Enterprise Edition (JEE) has always been a strong platform for building robust web applications, but one thing it’s often lacked is an…

Wishva Kalhara · 2025-05-23 03:45 · 0 claps · 3.1 min read
#java #j2ee #expressjs #router
Open on Medium ↗
Wiki topics: 🌐 · Web Development 📰 · Journalism & News

Olympus Router (Supports JEE7+)

Java Enterprise Edition (JEE) has always been a strong platform for building robust web applications, but one thing it’s often lacked is an elegant, expressive routing system similar to what Node.js developers enjoy with Express.js.

With this new lightweight, performant router for JEE7+ applications, developers can now write expressive, middleware-driven route definitions while maintaining the power and stability of the Java ecosystem.

Key Features

Express-style Middleware StackSupports HTTP Methods: GET, POST, PUT, DELETEPath Parameter Parsing: e.g., /users/:idCustom Middleware Composition: Chain or skip specific middleware functions ✅ Boolean-based Execution Flow: Middlewares act as filters and must return true to continue the chain

Installation

To get started, follow these three simple steps:

1. Add the Dependency

Add the router to your Maven project: Maven Repository: io.github.vishva-kalhara » olympus-router

<dependency>
    <groupId>io.github.vishva-kalhara</groupId>
    <artifactId>olympus-router</artifactId>
    <version>2.4.1</version>
</dependency>

2. Create a Concrete Router class

Extend RouterBase from the library to define your own concrete Router base. This gives you control over how to handle cases like unknown routes or other centralized error handling.

import io.github.vishvakalhara.olympus_router.RouterBase;

public class Router extends RouterBase {

    @Override
    public void handleEndpointNotFoundException(HttpServletResponse resp) throws IOException {
        ResponseHandler.sendEndpointNotFound(resp);
    }
}

You can customize the behavior of this method to return JSON errors, log incidents, or respond with custom messages as needed.

3. Create Your Routers by Extending the Concrete Router

Each API router should extend the Router class you've defined above. This helps keep your routers modular and your error-handling consistent across all endpoints.

@WebServlet(name = "CategoryRouter", urlPatterns = {"/api/v1/categories/*"})
public class CategoryRouter extends Router {

    private final CategoryController categoryController;

    public CategoryRouter() {
        categoryController = new CategoryController();
    }

    @Override
    public void init() throws ServletException {
        this
            .register(HttpMethod.PUT, "/:id", categoryController::updateCategory);  // Returns CategoryDTO
    }
}

Understanding the Middleware Stack

One of the standout features of this router is its middleware stack, inspired by the Express.js design philosophy. Middleware in this router serves as a chain of filters or processors that execute in sequence for each request.

Each middleware must implement a specific method signature defined by the RouteHandler functional interface:

@FunctionalInterface
public interface RouteHandler {

    boolean route(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException;
}

This interface ensures that every middleware function behaves consistently, allowing it to either:

  • Return true: allowing the request to flow to the next middleware or route handler.
  • Return false: halting the middleware chain immediately, ideal for blocking unauthorized access, early validation failures, or other control flow needs.

You can use Java lambdas or method references to implement this interface, making middleware clean, readable, and composable across routes.

The router supports two key methods for middleware management:

  • .use(middleware): Globally adds the middleware to all following route registrations.
  • .remove(middleware): Removes a previously added global middleware for subsequent routes, allowing fine-grained control.

Here’s an example that demonstrates how the middleware stack can be composed and controlled per route:

@WebServlet(name = "ProductsRouter", urlPatterns = "/api/v1/products/*")
public class ProductsRouter extends Router {

    private final ProductsController productsController;

    public ProductsRouter() {
        this.productsController = new ProductsController();
    }

    @Override
    public void init() {
        this.use(AuthMiddleware::getStoreIdFromApiKey) // Applied globally to the routes below
            .register(HttpMethod.GET, "/", productsController::getAllProducts)
            .register(HttpMethod.GET, "/:productId", productsController::getOneProducts)

            .remove(AuthMiddleware::getStoreIdFromApiKey) // Removed for the following route
            .register(HttpMethod.PUT, "/:productId", 
                      AuthMiddleware::authenticateByAdminOrCustomerAndGetStoreId, 
                      productsController::createProduct)

            .use(AuthMiddleware::authenticateByAdminAndGetStoreId) // Applied only to the remaining routes
            .register(HttpMethod.POST, "/", productsController::createProduct)
            .register(HttpMethod.DELETE, "/:productId", productsController::deleteProduct);
    }
}

In the example above:

  • AuthMiddleware::getStoreIdFromApiKey is first applied to GET routes.
  • It is then removed before registering the PUT route.
  • A different middleware, authenticateByAdminAndGetStoreId, is applied for POST and DELETE.

This level of flexibility makes the router powerful for real-world applications where certain endpoints require stricter access control than others, or when performance considerations require skipping unnecessary middleware.

Path Parameter Parsing

The router also supports dynamic path parameters, allowing developers to define routes with placeholders like /:id or /:productId. This makes it easy to map resource-specific endpoints while keeping route definitions clean and semantic.

For example, consider the following route:

.register(HttpMethod.DELETE, "/:productId", productsController::deleteProduct);

In this case, when a request is made to /api/v1/products/42, the router automatically extracts the value 42 and makes it available through the HttpServletRequest object as an attribute. To access it, use:

String productId = (String) req.getAttribute("param_productId");

The convention is that parameters defined with :name are stored with the key "param_name" in the request attributes. This pattern allows you to easily retrieve parameters in your controllers or middleware without additional parsing logic. It works seamlessly with all HTTP methods and makes route handling concise and intuitive.

Composition and Reusability

  • Middlewares can be reused across different routes.
  • You can selectively remove middlewares from specific routes if they are not required.
  • This keeps your code DRY, modular, and maintainable.

Conclusion

This router brings the simplicity and expressiveness of Express.js to Java, enabling faster development and cleaner APIs for JEE applications. Its middleware-based architecture and path parameter parsing make it an excellent choice for building RESTful services in a Java backend.

Start building with it today and bring modern routing to your enterprise applications!


메타데이터
post_id
4a62f73f997e
slug
olympus-router-supports-jee7-4a62f73f997e
url
https://medium.com/@vishvakalhara/olympus-router-supports-jee7-4a62f73f997e
canonical_url
https://medium.com/@vishvakalhara/olympus-router-supports-jee7-4a62f73f997e
author_url
https://medium.com/@vishvakalhara
status
ok
fetched_at
2026-06-26 03:39:16