REST Advanced Topics: The Superpowers of Your API 🚀
Imagine your REST API is a pizza delivery shop. So far, you’ve learned how to take orders and deliver pizzas. But now? You’re about to unlock the superpowers that make your shop legendary—live tracking, special packaging, faster deliveries, and VIP menus!
🌊 Server-Sent Events (SSE): The Live News Channel
What Is It?
Think of Server-Sent Events like a radio station. You tune in once, and music keeps flowing to you—no need to keep asking “What’s next?”
In regular REST, the client asks → server answers. That’s like calling the pizza shop every minute: “Is my order ready?” Annoying, right?
With SSE, the server pushes updates to you automatically. The pizza shop calls YOU when your order is ready!
How It Works
@GET
@Path("/updates")
@Produces(MediaType.SERVER_SENT_EVENTS)
public void getUpdates(
@Context SseEventSink sink,
@Context Sse sse) {
// Send message to client
sink.send(sse.newEvent("Order received!"));
sink.send(sse.newEvent("Pizza in oven!"));
sink.send(sse.newEvent("Out for delivery!"));
}
Why Use It?
- Live dashboards (stock prices, sports scores)
- Notifications (new messages, alerts)
- Progress tracking (file uploads, order status)
Key Point đź’ˇ
SSE is one-way: server → client. For two-way chat, you’d use WebSockets. But for live updates? SSE is simpler!
🛡️ Filters and Interceptors: The Security Guards & Editors
What Are They?
Imagine every pizza order passes through two checkpoints:
-
Security Guard (Filter): Checks if you’re allowed to order. Do you have an account? Is your payment valid?
-
Editor (Interceptor): Adjusts the order or wraps the pizza nicely. Maybe adds a napkin or corrects the address.
Filters: The Gatekeepers
Filters work on requests and responses. They run BEFORE your code (request filter) or AFTER (response filter).
@Provider
public class AuthFilter
implements ContainerRequestFilter {
@Override
public void filter(
ContainerRequestContext ctx) {
String token = ctx.getHeaderString(
"Authorization");
if (token == null) {
ctx.abortWith(
Response.status(401).build());
}
}
}
Interceptors: The Content Editors
Interceptors work on the message body—they can read, modify, or wrap it.
@Provider
public class LoggingInterceptor
implements ReaderInterceptor {
@Override
public Object aroundReadFrom(
ReaderInterceptorContext ctx)
throws IOException {
// Log before reading
System.out.println("Reading body...");
// Continue normal processing
return ctx.proceed();
}
}
When to Use Which?
| Use Case | Use This |
|---|---|
| Check auth tokens | Filter |
| Log requests | Filter |
| Compress response | Interceptor |
| Encrypt body | Interceptor |
đź’‰ Context Injection: Getting the Right Tools
What Is It?
Imagine you’re a chef. Sometimes you need a knife, sometimes a spoon. Context Injection gives you the right tool when you need it—automatically!
In Jakarta EE, the @Context annotation injects useful information about the current request.
What Can You Inject?
@Path("/order")
public class OrderResource {
@Context
private HttpServletRequest request;
@Context
private HttpHeaders headers;
@Context
private UriInfo uriInfo;
@Context
private SecurityContext security;
@GET
public String getOrder() {
// Get client's IP address
String ip = request.getRemoteAddr();
// Get a specific header
String auth = headers
.getHeaderString("Authorization");
// Get the full URL
String url = uriInfo
.getAbsolutePath().toString();
return "Request from: " + ip;
}
}
Common Context Objects
| Object | What It Gives You |
|---|---|
HttpHeaders |
All request headers |
UriInfo |
URL, path, query params |
SecurityContext |
Who’s logged in |
Request |
HTTP method info |
Providers |
Other JAX-RS providers |
⚡ Async REST Processing: Don’t Make Customers Wait!
The Problem
Imagine ordering pizza, and the cashier makes you wait at the counter while they bake it. The line behind you grows… people get angry!
Synchronous = You wait. Server waits. Everyone waits.
The Solution
With Async Processing, the server says: “Got it! I’ll call you when it’s ready!” Then it helps the next customer.
@GET
@Path("/slow-operation")
public void slowOperation(
@Suspended AsyncResponse response) {
// Handle in background thread
CompletableFuture.runAsync(() -> {
try {
// Simulate slow work (5 seconds)
Thread.sleep(5000);
// Send response when ready
response.resume("Done!");
} catch (Exception e) {
response.resume(e);
}
});
// Method returns immediately!
// Server is free to handle other requests
}
Setting Timeouts
Don’t let customers wait forever:
@GET
public void getData(
@Suspended AsyncResponse response) {
// Give up after 30 seconds
response.setTimeout(30, TimeUnit.SECONDS);
response.setTimeoutHandler(ar ->
ar.resume(Response
.status(503)
.entity("Try again later")
.build()));
}
Why Use Async?
- Long database queries
- Calling external APIs
- File processing
- Any slow operation!
📦 Multipart Form Data: Sending Files and More
What Is It?
Regular forms send text. But what if you want to upload a profile picture AND your name together? That’s where Multipart comes in!
Think of it like a gift box with compartments—one section for the photo, another for the card.
Receiving File Uploads
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(
@FormDataParam("file")
InputStream fileStream,
@FormDataParam("file")
FormDataContentDisposition fileDetail,
@FormDataParam("description")
String description) {
String fileName = fileDetail.getFileName();
// Save the file
saveFile(fileStream, fileName);
return Response.ok(
"Uploaded: " + fileName).build();
}
The HTML Form
<form method="POST"
enctype="multipart/form-data"
action="/api/upload">
<input type="file" name="file">
<input type="text" name="description">
<button type="submit">Upload</button>
</form>
Key Points đź’ˇ
- Use
@Consumes(MediaType.MULTIPART_FORM_DATA) - Each part has a name (like “file” or “description”)
- Files come as
InputStream - Metadata comes in
FormDataContentDisposition
🔢 API Versioning: Old Friends & New Friends
The Problem
Your pizza shop becomes famous! You add new menu items. But some old customers only know the old menu. How do you serve both?
API Versioning lets you run multiple versions side by side!
Strategy 1: URL Path Versioning (Most Common)
@Path("/v1/pizza")
public class PizzaResourceV1 {
@GET
public String getPizza() {
return "Classic Margherita";
}
}
@Path("/v2/pizza")
public class PizzaResourceV2 {
@GET
public PizzaDTO getPizza() {
return new PizzaDTO(
"Margherita",
12.99,
List.of("cheese", "tomato"));
}
}
Clients call: /api/v1/pizza or /api/v2/pizza
Strategy 2: Header Versioning
@Path("/pizza")
public class PizzaResource {
@GET
public Response getPizza(
@HeaderParam("API-Version")
String version) {
if ("2".equals(version)) {
return Response.ok(
new PizzaDTO(...)).build();
}
// Default to v1
return Response.ok(
"Classic Margherita").build();
}
}
Clients send header: API-Version: 2
Strategy 3: Query Parameter
@GET
public Response getPizza(
@QueryParam("version")
@DefaultValue("1")
String version) {
// Handle based on version
}
Clients call: /pizza?version=2
Which Should You Use?
| Strategy | Pros | Cons |
|---|---|---|
| URL Path | Clear, cacheable | URL changes |
| Header | Clean URLs | Less visible |
| Query | Easy to test | Messy URLs |
Tip: URL path versioning is the most common and easiest to understand!
đź”— HATEOAS: The Self-Guiding API
What Is HATEOAS?
Hypermedia As The Engine Of Application State
That’s a mouthful! Let’s simplify:
Imagine you’re in a video game. After defeating a monster, the game shows you buttons: “Go to Shop”, “Next Level”, “Save Game”. You don’t need a manual—the game TELLS you what you can do next!
HATEOAS makes your API work the same way. The response includes links to related actions.
Without HATEOAS (Old Way)
{
"orderId": 123,
"status": "preparing",
"total": 29.99
}
The client must know the API structure to do anything else.
With HATEOAS (Smart Way)
{
"orderId": 123,
"status": "preparing",
"total": 29.99,
"_links": {
"self": "/orders/123",
"cancel": "/orders/123/cancel",
"track": "/orders/123/tracking",
"payment": "/payments/order/123"
}
}
Now the client KNOWS what’s possible! They can cancel, track, or pay—without reading documentation.
Building HATEOAS Responses
@GET
@Path("/{id}")
public Response getOrder(
@PathParam("id") Long id,
@Context UriInfo uriInfo) {
Order order = findOrder(id);
// Build links
URI self = uriInfo
.getAbsolutePathBuilder()
.build();
URI cancel = uriInfo
.getBaseUriBuilder()
.path("orders/{id}/cancel")
.build(id);
// Create response with links
Map<String, Object> response =
new HashMap<>();
response.put("order", order);
response.put("_links", Map.of(
"self", self.toString(),
"cancel", cancel.toString()
));
return Response.ok(response).build();
}
Why HATEOAS Matters
- Self-documenting API
- Discoverable actions
- Flexible evolution (change URLs without breaking clients)
- Reduced coupling between client and server
🎯 Putting It All Together
Let’s see how these superpowers work in a real pizza ordering system:
graph TD A["📱 Client Request"] --> B{🛡️ Auth Filter} B -->|Invalid| C["❌ 401 Unauthorized"] B -->|Valid| D["📦 Interceptor<br>Log & Validate"] D --> E["💉 Context Injection<br>Get User Info"] E --> F{📋 Route} F -->|Upload Photo| G["📸 Multipart<br>Handle File"] F -->|Get Updates| H["🌊 SSE<br>Stream Events"] F -->|Long Query| I["⚡ Async<br>Background Work"] G --> J["🔗 HATEOAS Response"] H --> J I --> J J --> K["🔢 Version<br>v1 or v2 format"] K --> L["✅ Client Receives"]
📝 Quick Summary
| Feature | What It Does | Pizza Shop Analogy |
|---|---|---|
| SSE | Server pushes live updates | Shop calls YOU when ready |
| Filters | Check requests before/after | Security guard at door |
| Interceptors | Modify message body | Gift wrapper for pizza |
| Context Injection | Get request info automatically | Chef gets right tools |
| Async | Handle slow tasks without blocking | Take order, bake later |
| Multipart | Send files + data together | Box with compartments |
| Versioning | Run multiple API versions | Old menu + new menu |
| HATEOAS | Include action links in response | “What’s next?” buttons |
🚀 You Did It!
You’ve just unlocked the advanced superpowers of REST APIs! These aren’t just fancy features—they’re the tools that separate a basic API from a professional, production-ready service.
Remember:
- SSE for live updates
- Filters for security and logging
- Interceptors for body transformation
- Context for request information
- Async for slow operations
- Multipart for file uploads
- Versioning for evolution
- HATEOAS for self-describing APIs
Now go build something amazing! 🎉
