REST API
Build JSON APIs with simplified annotations.
Basic Controller
@REST("/api/users")
public class UserApi {
@GET
public List<User> list() {
return userService.findAll();
}
@GET("/:id")
public User get(@Path("id") Long id) {
return userService.findById(id);
}
@POST
public User create(@Body User user) {
return userService.save(user);
}
}Annotations
@REST("/api") // Base path for controller
@GET // GET request
@POST // POST request
@PUT // PUT request
@DEL // DELETE request
@Path("id") // Path parameter
@Body // Request body (JSON)
@Query("q") // Query parameterResponse Types
@GET("/users")
public List<User> users() { ... } // Auto-JSON
@GET("/download")
public ResponseEntity<byte[]> file() { ... }API docs auto-generated at /api/docs when openapi.enabled=true
Server-Sent Events
Push real-time updates to clients with SSE.
import com.osmig.Jweb.framework.http.SSE;
// Create SSE endpoint
app.get("/events", req -> SSE.stream(emitter -> {
// Send events to client
emitter.send("connected", "Hello!");
// Send periodic updates
while (!emitter.isClosed()) {
emitter.send("update", getData());
Thread.sleep(1000);
}
}));Event Types
// Named event (client listens with addEventListener)
emitter.send("notification", jsonData);
// Default event (client listens with onmessage)
emitter.send(jsonData);
// With event ID (for reconnection)
emitter.sendWithId("msg-123", "message", data);Client JavaScript
// Using Actions DSL
script()
.raw("""
const es = new EventSource('/events');
es.addEventListener('notification', (e) => {
const data = JSON.parse(e.data);
showNotification(data);
});
es.onerror = () => console.log('Connection lost');
""")
.build();Broadcasting
// Broadcast to all connected clients
SSE.broadcast("notifications", "New message!");
// Broadcast to specific topic
SSE.topic("orders").send("orderUpdate", orderData);
// Client subscribes to topic
app.get("/orders/updates", req -> {
String orderId = req.query("id");
return SSE.subscribe("orders-" + orderId);
});Complete Example
// Live dashboard updates
app.get("/dashboard/updates", req -> SSE.stream(emitter -> {
emitter.send("init", getDashboardData());
while (!emitter.isClosed()) {
var stats = getStats();
emitter.send("stats", stats);
Thread.sleep(5000);
}
}));SSE auto-reconnects on connection loss. Use event IDs for message replay.
Background Jobs
Run tasks asynchronously without blocking the request.
import com.osmig.Jweb.framework.async.Jobs;
// Fire and forget
Jobs.run(() -> {
sendWelcomeEmail(user);
logAnalytics(event);
});
// Return immediately, email sends in background
app.post("/register", req -> {
User user = createUser(req);
Jobs.run(() -> sendWelcomeEmail(user));
return Response.redirect("/welcome");
});Scheduled Jobs
// Run every 5 minutes
Jobs.every(Duration.ofMinutes(5), () -> {
cleanupExpiredSessions();
});
// Run every hour
Jobs.every(Duration.ofHours(1), () -> {
generateDailyReport();
});
// Run at specific time (cron-like)
Jobs.daily(LocalTime.of(2, 0), () -> {
runNightlyBackup();
});Delayed Execution
// Run after delay
Jobs.delay(Duration.ofSeconds(30), () -> {
sendReminderEmail(user);
});
// Run after 1 hour
Jobs.delay(Duration.ofHours(1), () -> {
expireTemporaryLink(linkId);
});With Result
// Get future result
CompletableFuture<Report> future = Jobs.submit(() -> {
return generateReport(params);
});
// Use when ready
future.thenAccept(report -> {
emailReport(report);
});
// Or block and wait
Report report = future.get();Error Handling
Jobs.run(() -> {
try {
processOrder(order);
} catch (Exception e) {
log.error("Order processing failed", e);
notifyAdmin(e);
}
});
// With retry
Jobs.retry(3, Duration.ofSeconds(5), () -> {
callExternalApi(data);
});Use Jobs for anything that doesn't need to block the HTTP response.