Skip to main content

Servlets & Request Handling

Sling Models render content into components. But sometimes you need a custom endpoint -- a URL that returns JSON for a frontend, handles a form POST, or performs an action. That is what a Sling servlet is for. This chapter shows how requests are routed to servlets and how to write them safely.

How Sling routes a request

Recall from The JCR & Sling that Sling resolves a request to a resource, then picks a script or servlet based on the resource's sling:resourceType, the selectors, the extension, and the HTTP method. A servlet is just another way to render a resource -- bound either to a resource type or to a fixed path.

GET /content/mysite/en/products.list.json
| | |
| | +-- extension: json
| +------- selector: list
+---------------- resource: the products page

Two ways to bind a servlet

BindingAnnotationUse when
Resource type (preferred)@SlingServletResourceTypesThe servlet renders a resource/component. Cacheable, RESTful, dispatcher-friendly
Path@SlingServletPathsA standalone utility endpoint not tied to content (use sparingly)

Prefer resource-type binding. Path-bound servlets bypass Sling's resource resolution and access control, are harder to cache, and must be explicitly allowed through the Dispatcher. Adobe discourages them for anything content-related.

Resource-type-bound servlet (returns JSON)

This servlet responds to <page>.children.json and returns the page's child pages as JSON:

core/.../servlets/ChildPagesServlet.java
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.google.gson.Gson;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
import org.osgi.service.component.annotations.Component;

import javax.servlet.Servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

@Component(service = Servlet.class)
@SlingServletResourceTypes(
resourceTypes = "mysite/components/page",
selectors = "children",
extensions = "json",
methods = "GET"
)
public class ChildPagesServlet extends SlingSafeMethodsServlet {

@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws IOException {

Resource resource = request.getResource();
PageManager pageManager = resource.getResourceResolver().adaptTo(PageManager.class);
Page current = pageManager != null ? pageManager.getContainingPage(resource) : null;

List<String> titles = new ArrayList<>();
if (current != null) {
Iterator<Page> children = current.listChildren();
while (children.hasNext()) {
titles.add(children.next().getTitle());
}
}

response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(new Gson().toJson(titles));
}
}

Safe vs all methods

Base classHandlesUse for
SlingSafeMethodsServletGET, HEAD (read-only)Read endpoints, JSON APIs
SlingAllMethodsServletGET, POST, PUT, DELETEEndpoints that modify content or handle forms

Extend the narrowest base class that does the job -- a read endpoint should not accept POST.

Handling a POST safely

A form handler extends SlingAllMethodsServlet and must validate input. Untrusted request parameters must never flow into JCR paths, queries, or commands (see the project security rules and Input Validation):

core/.../servlets/ContactFormServlet.java
@Component(service = Servlet.class)
@SlingServletResourceTypes(
resourceTypes = "mysite/components/contactform",
methods = "POST",
extensions = "json"
)
public class ContactFormServlet extends SlingAllMethodsServlet {

@Override
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws IOException {

String email = request.getParameter("email");

// Validate BEFORE using the value for anything.
if (email == null || !email.matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) {
response.setStatus(SlingHttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("{\"error\":\"invalid email\"}");
return;
}

// ... persist via a service user / send via a service, never an admin session ...

response.setContentType("application/json");
response.getWriter().write("{\"status\":\"ok\"}");
}
}

:::warning Validate every request parameter Request parameters, headers, and selectors are untrusted input. Validate and constrain them before using them in a query, a path, or a service call. Never build a JCR path by concatenating a raw parameter. See Security & Permissions. :::

Servlet or Sling Model Exporter?

If you only need to expose a component's data as JSON, you usually do not need a servlet -- a Sling Model Exporter (.model.json) is simpler and cacheable. Reach for a servlet when you need custom routing, query logic, an action endpoint, or a response shape that does not map to a single component.

Selectors, extensions, and caching

  • Selectors (page.children.json) let one resource expose multiple representations. They are part of the URL, so they are cacheable by the Dispatcher and CDN -- prefer them over query parameters for cacheable reads.
  • Extension sets the response type convention (.json, .csv, .xml).
  • Keep GET endpoints idempotent and cacheable; put anything that mutates behind POST.

Summary

You learned:

  • A servlet is a custom endpoint that renders a resource, routed like any Sling request
  • Prefer resource-type binding (@SlingServletResourceTypes) over path binding
  • Extend SlingSafeMethodsServlet for reads, SlingAllMethodsServlet for writes
  • Validate all request input before using it
  • Use selectors + extensions for cacheable representations
  • A Sling Model Exporter is often a simpler alternative to a read-only servlet

Official Documentation

Next up: Templates & Policies - editable templates, template types, component policies, and the Style System.