This page lists the common recipes beyond Hello World — sub-resources, providers, filters and interceptors, content negotiation, non-blocking async, SSE, and CDI integration via Vauban.

Sub-resources and sub-resource locators

@Path("/users")
public class UsersResource {

    // Sub-resource method
    @GET @Produces(MediaType.APPLICATION_JSON)
    public List<User> list() { ... }

    // Sub-resource locator: returns an instance that takes over dispatching
    @Path("/{id}")
    public UserResource user(@PathParam("id") long id) {
        return new UserResource(id);
    }
}

public class UserResource {
    private final long id;
    public UserResource(long id) { this.id = id; }

    @GET @Produces(MediaType.APPLICATION_JSON)
    public User get() { ... }

    @PUT @Consumes(MediaType.APPLICATION_JSON)
    public Response update(User u) { ... }
}

Providers — MessageBodyReader / MessageBodyWriter

Cassini ships CassiniJsonbReaderWriter covering application/json via Champollion. To add a custom format:

@Provider
@Produces("application/x-protobuf")
@Consumes("application/x-protobuf")
public class ProtobufProvider
        implements MessageBodyReader<Message>, MessageBodyWriter<Message> {
    // ...
}

Providers are registered via Application.getClasses(), the BeanProvider (CDI), or META-INF/services/jakarta.ws.rs.ext.MessageBodyReader. Best-match resolution is handled by MessageBodyRegistry in cassini-core.

ContextResolver and Providers

@Provider
public class JsonbContextResolver implements ContextResolver<Jsonb> {
    private final Jsonb jsonb = JsonbBuilder.create(
        new JsonbConfig().withFormatting(true));

    @Override public Jsonb getContext(Class<?> type) { return jsonb; }
}

Inject @Context Providers providers into a resource to fetch an arbitrary MessageBodyWriter or ContextResolver.

ExceptionMapper

@Provider
public class NotFoundExceptionMapper implements ExceptionMapper<NotFoundException> {
    @Override
    public Response toResponse(NotFoundException e) {
        return Response.status(Response.Status.NOT_FOUND)
            .entity(Map.of("error", e.getMessage()))
            .type(MediaType.APPLICATION_JSON)
            .build();
    }
}

Filters and interceptors

@Provider
@PreMatching
public class CorsFilter implements ContainerRequestFilter {
    @Override
    public void filter(ContainerRequestContext ctx) {
        ctx.getHeaders().add("Access-Control-Allow-Origin", "*");
    }
}

@Provider
@Logged                        // custom @NameBinding
public class LoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
    @Override public void filter(ContainerRequestContext req) {
        System.out.printf("[->] %s %s%n", req.getMethod(), req.getUriInfo().getPath());
    }
    @Override public void filter(ContainerRequestContext req, ContainerResponseContext res) {
        System.out.printf("[<-] %d%n", res.getStatus());
    }
}

Ordering is resolved via @Priority at stack startup (not per request).

Content negotiation

Cassini fully implements the §3.7 best-match rules and Request.selectVariant (§5.1):

@GET
public Response negotiate(@Context Request request) {
    var variants = Variant.mediaTypes(
        MediaType.APPLICATION_JSON_TYPE,
        MediaType.APPLICATION_XML_TYPE).build();
    var best = request.selectVariant(variants);
    if (best == null) return Response.notAcceptable(variants).build();
    return Response.ok(payload(), best).build();
}

Async — @Suspended AsyncResponse

@GET @Path("/long")
public void longRunning(@Suspended AsyncResponse async) {
    Thread.startVirtualThread(() -> {
        try {
            var result = compute();        // may block
            async.resume(result);
        } catch (Exception e) {
            async.resume(e);
        }
    });
}

Async execution leverages virtual threads (Java 25). The CassiniAsyncContext (internal SPI) drives CompletionStage propagation down to the transport. Full chunked streaming is scheduled for milestone M2h — see TCK status.

Server-Sent Events

@GET @Path("/events") @Produces(MediaType.SERVER_SENT_EVENTS)
public void events(@Context SseEventSink sink, @Context Sse sse) {
    try (sink) {
        for (int i = 0; i < 10; i++) {
            sink.send(sse.newEvent("tick-" + i));
        }
    }
}
The current implementation (CassiniSseEventSink) buffers then emits in bulk — spec-compliant per §11 on the contract, but real-time chunked streaming is scheduled for milestone M2i (see lien:https://codeberg.org/Vidocq/cassini/src/branch/main/ASYNC.md[ASYNC.md]).

JAX-RS Client

Add cassini-client to the path and ClientBuilder.newClient() returns a Cassini client (discovered via ServiceLoader). It is a zero-dependency Jakarta REST 4.0 Client built on java.net.http + virtual threads, sharing JSON-B (de)serialization with the server side.

try (Client client = ClientBuilder.newClient()) {
    User u = client.target("https://api.example.com")
        .path("/users/{id}").resolveTemplate("id", 42)
        .request(MediaType.APPLICATION_JSON)
        .get(User.class);
}

Supported: WebTarget URI building, ClientRequestFilter / ClientResponseFilter (@Priority-ordered), ReaderInterceptor / WriterInterceptor, async invocation on a virtual thread (.async().get()), and auto-registered Feature`s discovered via `ServiceLoader (e.g. MicroProfile Telemetry instrumentation — no explicit .register() needed).

Response r = client.target(base).path("/orders")
    .request()
    .async()                       // runs on a virtual thread
    .post(Entity.json(order))
    .get();

CDI / Vauban integration

With cassini-cdi-vauban on the classpath, VaubanBeanProvider.getResourceClasses() walks the BeanManager and exposes all @Path / @Provider classes to the stack. No manual list of singletons is required.

var container = VaubanContainer.builder()
        .addBeanClass(TodoService.class)
        .addBeanClass(TodoResource.class)
        .build();

SeBootstrap.start(new Application() {}, config);   // Cassini scans via Vauban

The CassiniScopeExtension (BCE) automatically adds @RequestScoped to @Path classes lacking an explicit scope. See internals for the diagram.