HTTP handlers process incoming HTTP requests and generate responses in Vespa’s container. They enable you to implement custom REST APIs, webhooks, admin interfaces, and specialized request processing logic.Documentation Index
Fetch the complete documentation index at: https://support.agentrank.io/llms.txt
Use this file to discover all available pages before exploring further.
Overview
HTTP handlers allow you to:- Implement REST APIs with custom business logic
- Create custom endpoints for monitoring, administration, or webhooks
- Process non-standard requests that don’t fit the search or feed APIs
- Integrate external systems through HTTP interfaces
- Build specialized protocols on top of HTTP
Handler Types
Vespa provides two main base classes for HTTP handlers:ThreadedHttpRequestHandler
For synchronous request processing with automatic thread management:import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
import java.io.IOException;
import java.io.OutputStream;
public class MyHandler extends ThreadedHttpRequestHandler {
@Override
public HttpResponse handle(HttpRequest request) {
// Process request and return response
return new JsonResponse(200, "{\"status\":\"ok\"}");
}
private static class JsonResponse extends HttpResponse {
private final String json;
JsonResponse(int status, String json) {
super(status);
this.json = json;
}
@Override
public void render(OutputStream output) throws IOException {
output.write(json.getBytes());
}
@Override
public String getContentType() {
return "application/json";
}
}
}
ThreadedRequestHandler
For lower-level request handling with full control over request/response lifecycle:import com.yahoo.jdisc.handler.ThreadedRequestHandler;
import com.yahoo.jdisc.handler.ResponseHandler;
import com.yahoo.jdisc.handler.BufferedContentChannel;
import com.yahoo.jdisc.Request;
import com.yahoo.jdisc.Response;
import java.util.concurrent.Executor;
public class LowLevelHandler extends ThreadedRequestHandler {
public LowLevelHandler(Executor executor) {
super(executor);
}
@Override
protected void handleRequest(Request request,
BufferedContentChannel requestContent,
ResponseHandler responseHandler) {
Response response = new Response(200);
responseHandler.handleResponse(response);
}
}
Basic Handler Structure
A simple handler returning JSON:package com.example;
import com.yahoo.component.annotation.Inject;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executor;
public class HelloHandler extends ThreadedHttpRequestHandler {
@Inject
public HelloHandler(Executor executor) {
super(executor);
}
@Override
public HttpResponse handle(HttpRequest request) {
String name = request.getProperty("name");
if (name == null) {
name = "World";
}
return new StringResponse(200,
String.format("{\"message\":\"Hello, %s!\"}", name));
}
}
Request Processing
Reading Request Data
@Override
public HttpResponse handle(HttpRequest request) {
// Get query parameters
String param = request.getProperty("param");
// With default value
String value = request.getProperty("key", "default");
// All parameters
Map<String, String> params = request.propertyMap();
return new MyResponse(params);
}
URI and Path Handling
@Override
public HttpResponse handle(HttpRequest request) {
// Get full URI
URI uri = request.getUri();
// Get path
String path = uri.getPath();
// Parse path segments
String[] segments = path.split("/");
if (segments.length > 2) {
String resourceType = segments[1];
String resourceId = segments[2];
return handleResource(resourceType, resourceId);
}
return new ErrorResponse(404, "Not found");
}
Response Generation
Custom Response Classes
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class JsonResponse extends HttpResponse {
private final String jsonContent;
public JsonResponse(int status, String jsonContent) {
super(status);
this.jsonContent = jsonContent;
}
@Override
public void render(OutputStream output) throws IOException {
output.write(jsonContent.getBytes(StandardCharsets.UTF_8));
}
@Override
public String getContentType() {
return "application/json; charset=utf-8";
}
}
public class ErrorResponse extends JsonResponse {
public ErrorResponse(int status, String message) {
super(status,
String.format("{\"error\":\"%s\",\"status\":%d}",
message, status));
}
}
Setting Response Headers
@Override
public HttpResponse handle(HttpRequest request) {
MyResponse response = new MyResponse();
// Set custom headers
response.headers().put("X-Custom-Header", "value");
response.headers().put("Cache-Control", "no-cache");
response.headers().put("Access-Control-Allow-Origin", "*");
return response;
}
Streaming Responses
import java.io.OutputStream;
public class StreamingResponse extends HttpResponse {
private final DataSource dataSource;
public StreamingResponse(DataSource source) {
super(200);
this.dataSource = source;
}
@Override
public void render(OutputStream output) throws IOException {
// Stream data in chunks
byte[] buffer = new byte[8192];
int bytesRead;
try (var stream = dataSource.openStream()) {
while ((bytesRead = stream.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
output.flush();
}
}
}
@Override
public String getContentType() {
return "application/octet-stream";
}
}
Common Handler Patterns
REST API Handler
import com.fasterxml.jackson.databind.ObjectMapper;
public class RestApiHandler extends ThreadedHttpRequestHandler {
private final ObjectMapper objectMapper = new ObjectMapper();
private final DataService dataService;
@Inject
public RestApiHandler(Executor executor, DataService dataService) {
super(executor);
this.dataService = dataService;
}
@Override
public HttpResponse handle(HttpRequest request) {
String method = request.getMethod().name();
String path = request.getUri().getPath();
try {
return switch (method) {
case "GET" -> handleGet(path, request);
case "POST" -> handlePost(path, request);
case "PUT" -> handlePut(path, request);
case "DELETE" -> handleDelete(path, request);
default -> methodNotAllowed();
};
} catch (Exception e) {
log.log(Level.SEVERE, "Request processing failed", e);
return new ErrorResponse(500, "Internal server error");
}
}
private HttpResponse handleGet(String path, HttpRequest request) {
String id = extractId(path);
var data = dataService.get(id);
if (data == null) {
return new ErrorResponse(404, "Not found");
}
return new JsonResponse(200, toJson(data));
}
private HttpResponse handlePost(String path, HttpRequest request)
throws IOException {
String body = new String(
request.getData().readAllBytes(),
StandardCharsets.UTF_8
);
var data = objectMapper.readValue(body, MyData.class);
var created = dataService.create(data);
return new JsonResponse(201, toJson(created));
}
private String extractId(String path) {
String[] parts = path.split("/");
return parts.length > 2 ? parts[2] : null;
}
private String toJson(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Health Check Handler
public class HealthHandler extends ThreadedHttpRequestHandler {
private final List<HealthCheck> checks;
@Inject
public HealthHandler(Executor executor,
ComponentRegistry<HealthCheck> checks) {
super(executor);
this.checks = new ArrayList<>(checks.allComponents());
}
@Override
public HttpResponse handle(HttpRequest request) {
boolean allHealthy = true;
StringBuilder result = new StringBuilder("{\"checks\":[");
for (int i = 0; i < checks.size(); i++) {
HealthCheck check = checks.get(i);
boolean healthy = check.isHealthy();
allHealthy &= healthy;
if (i > 0) result.append(",");
result.append(String.format(
"{\"name\":\"%s\",\"healthy\":%b}",
check.getName(), healthy
));
}
result.append("],\"status\":\"");
result.append(allHealthy ? "healthy" : "unhealthy");
result.append("\"}");
int status = allHealthy ? 200 : 503;
return new JsonResponse(status, result.toString());
}
}
Real-World Example: VipStatusHandler
From~/workspace/source/container-disc/src/main/java/com/yahoo/container/handler/VipStatusHandler.java:31:
import com.yahoo.container.core.VipStatusConfig;
import com.yahoo.jdisc.Metric;
public final class VipStatusHandler extends ThreadedHttpRequestHandler {
private final boolean accessDisk;
private final File statusFile;
private final VipStatus vipStatus;
@Inject
public VipStatusHandler(VipStatusConfig vipConfig,
Metric metric,
VipStatus vipStatus) {
// Dedicated thread pool to avoid returning errors when
// regular pool is at capacity
super(new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100)),
metric);
this.accessDisk = vipConfig.accessdisk();
this.statusFile = new File(vipConfig.statusfile());
this.vipStatus = vipStatus;
}
@Override
public HttpResponse handle(HttpRequest request) {
if (metric != null) {
metric.add("jdisc.http.requests.status", 1, null);
}
return new StatusResponse();
}
class StatusResponse extends HttpResponse {
private byte[] data = null;
private File file = null;
StatusResponse() {
super(200);
if (vipStatus != null && !vipStatus.isInRotation()) {
setOutOfServiceStatus();
} else if (accessDisk) {
checkFile();
} else {
data = "<title>OK</title>\n".getBytes();
}
}
@Override
public void render(OutputStream stream) throws IOException {
if (file != null) {
Files.copy(file.toPath(), stream);
} else if (data != null) {
stream.write(data);
}
stream.close();
}
private void setOutOfServiceStatus() {
data = "No search backends available".getBytes();
setStatus(404);
}
@Override
public String getContentType() {
return file != null ? "text/html" : "text/plain";
}
}
}
Handler Configuration
Register handlers inservices.xml:
<container id="default" version="1.0">
<handler id="com.example.HelloHandler" bundle="my-bundle">
<binding>http://*/hello</binding>
<binding>http://*/hello/*</binding>
</handler>
<handler id="com.example.RestApiHandler" bundle="my-bundle">
<binding>http://*/api/v1/*</binding>
</handler>
<handler id="com.example.HealthHandler" bundle="my-bundle">
<binding>http://*/health</binding>
</handler>
</container>
Handler Initialization
With custom configuration:import com.yahoo.component.annotation.Inject;
public class ConfiguredHandler extends ThreadedHttpRequestHandler {
private final String configValue;
private final int timeout;
@Inject
public ConfiguredHandler(Executor executor, MyHandlerConfig config) {
super(executor);
this.configValue = config.value();
this.timeout = config.timeout();
setTimeout(timeout, TimeUnit.SECONDS);
}
}
Async Response Handling
For long-running operations:import com.yahoo.container.jdisc.AsyncHttpResponse;
import java.util.concurrent.CompletableFuture;
public class AsyncHandler extends ThreadedHttpRequestHandler {
private final AsyncService asyncService;
@Inject
public AsyncHandler(Executor executor, AsyncService service) {
super(executor);
this.asyncService = service;
}
@Override
public HttpResponse handle(HttpRequest request) {
return new AsyncHttpResponse(200) {
@Override
public void render(OutputStream output,
ContentChannel channel,
CompletionHandler handler) {
// Start async operation
CompletableFuture.supplyAsync(() ->
asyncService.fetchData()
).thenAccept(data -> {
try {
output.write(data.getBytes());
output.flush();
} catch (IOException e) {
log.warning("Failed to write response: " + e);
} finally {
handler.completed();
}
}).exceptionally(ex -> {
handler.failed(ex);
return null;
});
}
};
}
}
Testing Handlers
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.jdisc.http.HttpRequest.Method;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import static org.junit.jupiter.api.Assertions.*;
public class MyHandlerTest {
@Test
public void testHandler() throws Exception {
MyHandler handler = new MyHandler(Runnable::run);
// Create test request
HttpRequest request = HttpRequest.createTestRequest(
"http://localhost/test?param=value",
Method.GET
);
// Handle request
HttpResponse response = handler.handle(request);
// Verify response
assertEquals(200, response.getStatus());
ByteArrayOutputStream out = new ByteArrayOutputStream();
response.render(out);
String content = out.toString();
assertTrue(content.contains("expected"));
}
}
Security Considerations
- Validate all input: Never trust user input
- Use authentication: Implement proper auth for sensitive endpoints
- Rate limiting: Protect against abuse
- HTTPS: Use TLS for production deployments
Performance Tips
- Use dedicated thread pools for handlers with different characteristics
- Stream large responses instead of buffering in memory
- Set appropriate timeouts with
setTimeout() - Cache expensive computations
- Monitor handler latency and throughput
Next Steps
- Learn about Searchers for query processing
- Explore Document Processors for feed processing
- See Plugins and Bundles for packaging handlers