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 parameter

Response 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.