Handling web requests
Fluxzero supports first-class WebRequest handling via the @HandleWeb annotation and its HTTP-specific
variants such as @HandleGet, @HandlePost, @HandleDelete, etc.
Instead of exposing a public HTTP server per application, Fluxzero uses a central Web Gateway that proxies all external
HTTP(S) and WebSocket traffic into the Runtime as WebRequest messages. These messages are:
- Logged for traceability and auditing
- Routed to client applications using the same handler system as for commands, events, and queries
- Handled by consumer applications which return a
WebResponse
Why this design?
Section titled “Why this design?”This architecture enables several key benefits:
- Zero exposure: client apps do not require a public-facing HTTP server and are thus invisible to attackers
- Back-pressure support: applications control load by polling their own messages
- Audit-friendly: every incoming request is automatically logged and correlated to its response
- Multiple consumers possible: multiple handlers can react to a
WebRequest, though typically only one produces the response (others usepassive = true)
Example
Section titled “Example”@HandleGet("/users")public List<UserAccount> listUsers() { return userService.getAllUsers();}@HandleGet("/users")fun listUsers(): List<UserAccount> { return userService.getAllUsers()}This will match incoming GET /users requests and return a list of users. The response is published as a WebResponse.
Use the general-purpose @HandleWeb annotation if you want to support multiple paths, multiple methods, or define a custom method:
@HandleWeb(value = "/users/{userId}", method = {"GET", "DELETE"})public CompletableFuture<?> handleUserRequest(WebRequest request, @PathParam String userId) { return switch (request.getMethod()) { case "GET" -> Fluxzero.query(new GetUser(userId)); case "DELETE" -> Fluxzero.sendCommand(new DeleteUser(userId)); default -> throw new UnsupportedOperationException(); };}@HandleWeb(value = ["/users/{userId}"], method = ["GET", "DELETE"])fun handleUserRequest(request: WebRequest, @PathParam userId: String): CompletableFuture<*> { return when (request.method) { "GET" -> Fluxzero.query(GetUser(userId)) "DELETE" -> Fluxzero.sendCommand(DeleteUser(userId)) else -> throw UnsupportedOperationException() }}@HandleWeb(value = "/users/{userId}", method = HttpRequestMethod.ANY)public CompletableFuture<?> handleAllUserMethods(WebRequest request, @PathParam String userId) { // Handle any method (GET, POST, DELETE, etc.) ...}@HandleWeb(value = ["/users/{userId}"], method = HttpRequestMethod.ANY)fun handleAllUserMethods(request: WebRequest, @PathParam userId: String): CompletableFuture<*> { // Handle any method (GET, POST, DELETE, etc.) ...}Suppressing a response
Section titled “Suppressing a response”If you want to listen to a WebRequest without returning a response—e.g. for monitoring or audit logging—use passive = true:
@HandlePost(value = "/log", passive = true)public void log(WebRequest request) { logService.store(request);}@HandlePost(value = "/log", passive = true)fun log(request: WebRequest) { logService.store(request)}Dynamic path parameters
Section titled “Dynamic path parameters”Use @PathParam to extract dynamic segments from the URI:
@HandleGet("/users/{id}")public UserAccount getUser(@PathParam String id) { return userService.get(id);}@HandleGet("/users/{id}")fun getUser(@PathParam id: String): UserAccount { return userService.get(id)}If no name is provided to @PathParam, the parameter name is used by default.
Other parameter annotations
Section titled “Other parameter annotations”You can also use these to extract other parts of the request:
@QueryParam– extract query string values@HeaderParam– extract headers@CookieParam– extract cookies@FormParam– extract form-encoded values (e.g., from POST body)
URI prefixing and composition with @Path
Section titled “URI prefixing and composition with @Path”Use @Path on packages, classes, methods, and properties to construct URI paths. Paths are composed from outermost to innermost.
- A path starting with
/resets the chain - Empty paths use the simple name of the enclosing package or class
- If applied to a property (field or getter), its value is used as a dynamic segment
@Pathpackage my.example.api;
@Path("users")public class UserHandler {
@Path("{id}") @HandleGet public User getUser(@PathParam String id) { ... }}This matches /api/users/{id}. If @Path("/users") had been used on the class instead, it would reset the prefix and match /users/{id}.
Serving static files
Section titled “Serving static files”Use @ServeStatic to serve static content directly from your app without an external web server:
@ServeStatic(value = "/web", resourcePath = "/static")public class WebAssets {}This serves files from /static (in classpath or filesystem) under the URI path /web/**.
Features
Section titled “Features”- Supports classpath and file system resources
- Optional fallback file (e.g. for SPAs)
- Clean URLs for statically exported routes
- Smart
Cache-Controlheaders - GZIP and Brotli compression (if precompressed assets are present)
Full annotation example
Section titled “Full annotation example”@ServeStatic( value = "/assets", resourcePath = "/public", fallbackFile = "index.html", cleanUrls = true, immutableCandidateExtensions = {"js", "css", "svg"}, maxAgeSeconds = 86400)Parameters:
Section titled “Parameters:”value: Web URI path(s) to exposeresourcePath: Filesystem or classpath rootfallbackFile: Fallback for unknown paths (e.g.index.html)cleanUrls: Whether extensionless paths first try<path>.htmland<path>/index.htmlbefore the fallbackimmutableCandidateExtensions: Enables long-lived caching for fingerprinted assetsmaxAgeSeconds: Cache duration for other resources
Example: serving a React app
Section titled “Example: serving a React app”@ServeStatic(value = "/app", resourcePath = "/static", fallbackFile = "index.html")public class WebFrontend {}This serves:
/app/index.html,/app/main.js, etc. from/static/app/onboardingfrom/static/onboarding.htmlor/static/onboarding/index.htmlwhen clean URLs are enabled- Falls back to
index.htmlfor client-side routing
Set cleanUrls = false if extensionless paths should only try the exact path before the fallback.
To restrict resolution:
- Use
classpath:/...to only serve classpath files - Use
file:/...to only serve local files
Combining static and dynamic handlers
Section titled “Combining static and dynamic handlers”You can mix @ServeStatic with dynamic handlers:
@Path("/app")@ServeStatic("static")public class AppController {
@HandleGet("/status") public Status getStatus() { return new Status("OK", Instant.now()); }
@HandlePost("/submit") public SubmissionResult submitForm(FormData data) { return formService.handle(data); }}This will:
- Serve
/app/static/**from thestatic/directory - Also handle
/app/statusand/app/submitdynamically
© 2026 Fluxzero