All AEM as a Cloud Service skills: component development, Workflow model design, development, triggering, launchers, debugging, and triaging, Dispatcher config authoring, advisory, incident response, performance tuning, and security hardening, Rapid Development Environment (RDE) deployment, log inspection, snapshots, and troubleshooting via `aio aem rde`, Jackrabbit Oak query/index tuning (property, Lucene index definitions), and extracting/inventorying JCR queries (XPath, SQL2, QueryBuilder) across a codebase.
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Placeholders:
[project]= project name,[package]= Java package (fromaem-conventions.md)
| Component Behavior | Servlet Needed? |
|---|---|
| Static content from dialog | No |
| Content from JCR (author-configured) | No |
| Real-time data from external API | Yes |
| Form submission with validation | Yes |
| Async content loading | Yes |
| Search results based on user query | Yes |
core/src/main/java/[package-path]/servlets/{ComponentName}Servlet.java
core/src/test/java/[package-path]/servlets/{ComponentName}ServletTest.javaAlways use javax.json - guaranteed available in AEM, no dependency conflicts.
import javax.json.Json;
import javax.json.JsonObjectBuilder;
import javax.json.JsonArrayBuilder;
import javax.json.JsonReader;
// Build JSON
JsonObjectBuilder result = Json.createObjectBuilder();
result.add("success", true);
result.add("data", Json.createObjectBuilder().add("key", "value"));
response.getWriter().write(result.build().toString());
// Parse JSON
try (JsonReader reader = Json.createReader(new StringReader(jsonString))) {
JsonObject obj = reader.readObject();
}package [package].servlets;
import java.io.IOException;
import javax.json.Json;
import javax.json.JsonObjectBuilder;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
import org.osgi.service.component.annotations.Component;
@Component(service = Servlet.class)
@SlingServletResourceTypes(
resourceTypes = "[project]/components/{component-name}",
methods = HttpConstants.METHOD_GET,
extensions = "json",
selectors = "data"
)
public class ComponentNameServlet extends SlingSafeMethodsServlet {
@Override
protected void doGet(SlingHttpServletRequest request,
SlingHttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
JsonObjectBuilder result = Json.createObjectBuilder();
try {
result.add("success", true);
result.add("data", Json.createObjectBuilder().add("key", "value"));
} catch (Exception e) {
response.setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR);
result.add("success", false);
result.add("error", "An error occurred");
}
response.getWriter().write(result.build().toString());
}
}URL Pattern: /content/mysite/page/jcr:content/component.data.json
package [package].servlets;
import java.io.IOException;
import javax.json.Json;
import javax.json.JsonObjectBuilder;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
import org.osgi.service.component.annotations.Component;
@Component(service = Servlet.class)
@SlingServletResourceTypes(
resourceTypes = "[project]/components/{component-name}",
methods = HttpConstants.METHOD_POST,
extensions = "json",
selectors = "submit"
)
public class ComponentNameServlet extends SlingAllMethodsServlet {
@Override
protected void doPost(SlingHttpServletRequest request,
SlingHttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
JsonObjectBuilder result = Json.createObjectBuilder();
try {
String name = request.getParameter("name");
// Validate and process...
result.add("success", true);
result.add("message", "Submitted successfully");
} catch (IllegalArgumentException e) {
response.setStatus(SlingHttpServletResponse.SC_BAD_REQUEST);
result.add("success", false);
result.add("error", e.getMessage());
}
response.getWriter().write(result.build().toString());
}
}// Success
{ "success": true, "data": { ... } }
// Error
{ "success": false, "error": "User-friendly message" }| Status Code | When to Use |
|---|---|
200 | Success |
400 | Invalid input |
403 | Access denied |
404 | Not found |
500 | Server error |
HTL - Expose resource path:
<div data-cmp-is="componentname" data-resource-path="${resource.path}">JavaScript - Fetch pattern:
var url = element.dataset.resourcePath + '.data.json';
fetch(url, { method: 'GET', credentials: 'same-origin' })
.then(function(response) { return response.json(); })
.then(function(result) {
if (result.success) { /* handle data */ }
else { /* handle error */ }
});POST servlets are protected by AEM's CSRF filter. The frontend must obtain a token and include it with every POST request. GET requests do not require CSRF tokens.
Step 1 — Fetch the CSRF token:
function getCsrfToken() {
return fetch('/libs/granite/csrf/token.json', {
credentials: 'same-origin'
})
.then(function(response) { return response.json(); })
.then(function(data) { return data.token; });
}Step 2 — Submit a POST request with the token:
getCsrfToken().then(function(token) {
fetch(element.dataset.resourcePath + '.submit.json', {
method: 'POST',
credentials: 'same-origin',
headers: {
'CSRF-Token': token,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'title=' + encodeURIComponent(title)
})
.then(function(response) { return response.json(); })
.then(function(result) {
if (result.success) { /* handle success */ }
else { /* handle error */ }
});
});Alternative — Send the token as a form parameter:
var body = ':cq_csrf_token=' + encodeURIComponent(token)
+ '&title=' + encodeURIComponent(title);Note: GET servlets do not need CSRF protection — only POST (and other state-changing methods) are filtered.
package [package].servlets;
import static org.junit.jupiter.api.Assertions.*;
import java.io.StringReader;
import javax.json.Json;
import javax.json.JsonObject;
import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
@ExtendWith(AemContextExtension.class)
class ComponentNameServletTest {
private final AemContext context = new AemContext();
@Test
void testDoGet_Success() throws Exception {
ComponentNameServlet servlet = new ComponentNameServlet();
context.create().resource("/content/test", "sling:resourceType", "[project]/components/name");
context.currentResource("/content/test");
servlet.doGet(context.request(), context.response());
// IMPORTANT: Use startsWith() for content type (see note below)
assertTrue(context.response().getContentType().startsWith("application/json"));
JsonObject result = Json.createReader(
new StringReader(context.response().getOutputAsString())).readObject();
assertTrue(result.getBoolean("success"));
}
}NEVER use exact match for content type in servlet tests:
// WRONG - Will fail because mock includes charset
assertEquals("application/json", context.response().getContentType());
// CORRECT - Use startsWith() to handle charset suffix
assertTrue(context.response().getContentType().startsWith("application/json"));The MockSlingHttpServletResponse.getContentType() returns "application/json;charset=UTF-8" when both content type and character encoding are set.
| Attribute | GET Servlet | POST Servlet |
|---|---|---|
| Parent class | SlingSafeMethodsServlet | SlingAllMethodsServlet |
| Method | HttpConstants.METHOD_GET | HttpConstants.METHOD_POST |
| Typical selector | data, search | submit |
| Use case | Read data | Write/submit data |
| Anti-Pattern | Why It's Bad | Better Approach |
|---|---|---|
| Path-based binding for component servlets | Bypasses ACLs, requires dispatcher config | Use @SlingServletResourceTypes |
| Exposing stack traces | Security risk, reveals internal details | Log server-side, return generic message |
| Hardcoded content paths | Breaks in different environments | Use resource-relative paths |
| No input validation | XSS, injection vulnerabilities | Validate and sanitize all input |
| Synchronous blocking calls | Poor performance | Use async patterns where possible |
| Ignoring HTTP status codes | Clients can't handle errors properly | Return appropriate status codes |
| No CSRF protection on POST | Vulnerable to CSRF attacks | Use Granite CSRF framework |
| Returning sensitive data | Privacy/security issues | Only return necessary data |
[package].servlets{ComponentName}Servlet convention@SlingServletResourceTypes uses correct resourceTypesSlingSafeMethodsServlet or SlingAllMethodsServlet)application/json)UTF-8)javax.json (not GSON)data-resource-pathpath.selector.extension).tessl-plugin
skills
aem-rde
aem-workflow
code-assessment
content-distribution
create-component
assets
references
dispatcher
incident-response
references
dispatcher-foundation
performance-tuning
references
dispatcher-foundation
security-hardening
references
dispatcher-foundation
technical-advisory
references
dispatcher-foundation
workflow-orchestrator
references
dispatcher-foundation
ensure-agents-md
references
migration
references
dispatcher
legacy-ui
template-modernization
validation
scripts
dispatcher-tool
validate-migration