SlingInternalRequest: A Cleaner Way to Reuse OOTB AEM Servlets with Internal API Access


When building features in Adobe Experience Manager (AEM), there are times you need to leverage servlet logic that already exists—especially when it interacts with internal AEM APIs not available through public interfaces.

For example, I recently needed to programmatically create Live Copies for assets. AEM doesn’t expose public Java APIs for this directly, but the WCMCommand Servlet performs this exact action behind the scenes.

Instead of reinventing the wheel or making a fragile external HTTP call, I used SlingInternalRequest API to invoke the servlet internally—within the same session.

Let’s dive into this approach.

The Naive Way: Using HTTP Calls

You might be tempted to do this:

URL url = new URL("http://localhost:4502/bin/wcmcommand");

HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
// set headers, auth, body etc.

But here’s why this is not ideal:

ProblemWhy It’s Bad
Network OverheadEven though it’s localhost, it’s still an external call
PerformanceHTTP serialization/deserialization adds latency

The Better Way: Use SlingInternalRequest

Instead of external HTTP calls, SlingInternalRequest lets you invoke servlets internally—just like a native method call, but within Sling’s lifecycle and context.

Step-1: Declare dependency on servlets-helpers

Ensure that your bundle includes a dependency to org.apache.sling.servlets-helpers:

<dependency>
  <groupId>org.apache.sling</groupId>
  <artifactId>org.apache.sling.servlets-helpers</artifactId>
  <version>1.4.6</version> <!-- Check for latest -->
</dependency>

If the dependency is not available in AEM, include it by embedding it through the pom.xml of “all” module. This approach will also ensure that the servlet-helpers bundle gets installed in AEM.

<embedded>
    <groupId>org.apache.sling</groupId>
    <artifactId>org.apache.sling.servlet-helpers</artifactId>
    <type>jar</type>
    <target>/apps/techrevel-vendor-packages/application/install</target>
</embedded>

Step-2: Create Post/Get Servlets which are to be accessed from an AEM Service

Sample POST Servlet

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.Servlet;
import java.io.IOException;

/**
 * A simple AEM servlet that handles POST requests, with parameters 'name' and 'email', the servlet returns a plain text response echoing back those values.
 */
@Component(service = {Servlet.class}, property = {
        "sling.servlet.paths=" + SimplePostServlet.SERVLET_PATH,
        "sling.servlet.methods=POST"
})
public class SimplePostServlet extends SlingAllMethodsServlet {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    public static final String SERVLET_PATH = "/bin/simplePostServlet";

    @Override
    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws IOException {
        // Set the response content type to plain text
        response.setContentType("text/plain");

        // Extract request parameters 'name' and 'email'
        String name = request.getParameter("name");
        String email = request.getParameter("email");

        // Write response content showing received parameters
        response.getWriter().write("name = " + name + " & email = " + email);
    }
}

Sample GET Example — Reuse Any Data Servlet

import com.day.cq.commons.jcr.JcrConstants;
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.HttpConstants;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.propertytypes.ServiceDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;

/**
 * A simple example of an AEM Sling GET servlet that responds with the `jcr:title`
 * property of the requested resource.
 */
@Component(service = { Servlet.class })
@SlingServletResourceTypes(
        resourceTypes = "weretail/components/structure/page", 
        methods = HttpConstants.METHOD_GET,                    
        extensions = "txt"                              
)
public class SimpleGetServlet extends SlingSafeMethodsServlet {
    private static final long serialVersionUID = 1L;

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected void doGet(final SlingHttpServletRequest req,
                         final SlingHttpServletResponse resp) throws ServletException, IOException {
        logger.info("Inside SimpleGetServlet doGet method");

        // Retrieve the resource targeted by the current request
        final Resource resource = req.getResource();

        // Set response content type
        resp.setContentType("text/plain");

        // Write the jcr:title property (if available) to the response
        String title = resource.getValueMap().get(JcrConstants.JCR_TITLE, String.class);
        resp.getWriter().write("Title = " + (title != null ? title : "No title found"));
    }
}

Step-3: Access Servlet in an AEM Service

This code demonstrates how to programmatically simulate GET and POST servlet calls within AEM using SlingInternalRequest

import com.techrevel.core.services.SampleSlingInternalRequest;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.engine.SlingRequestProcessor;
import org.apache.sling.servlethelpers.internalrequests.SlingInternalRequest;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

@Component(
        service = SampleSlingInternalRequest.class,
        immediate = true
)
public class SampleSlingInternalRequestImpl implements SampleSlingInternalRequest {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Reference
    private ResourceResolverFactory resolverFactory;

    @Reference
    private SlingRequestProcessor slingRequestProcessor;

    private static final String SUBSERVICE_NAME = "service-user";

    @Override
    public void callServlets() {
        Map<String, Object> param = new HashMap<>();
        param.put(ResourceResolverFactory.SUBSERVICE, SUBSERVICE_NAME);

        try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(param)) {

            // --- POST Servlet Call ---
            // Simulates a POST request to /bin/simplePostServlet with parameters
            String postResponse = new SlingInternalRequest(resolver, slingRequestProcessor, "/bin/simplePostServlet")
                    .withParameter("name", "Aanchal")
                    .withParameter("email", "abc@gmail.com")
                    .withRequestMethod("POST")
                    .execute()
                    .checkStatus(200) // Validates that response status is 200
                    .checkResponseContentType("text/plain")
                    .getResponseAsString(); // Retrieves the servlet response as a string

            logger.info("Response from POST: {}", postResponse);


            // --- GET Servlet Call ---
            // Simulates a GET request to a specific page content
            String getResponse = new SlingInternalRequest(resolver, slingRequestProcessor,
                    "/content/we-retail/language-masters/en/jcr:content")
                    .withExtension("txt") // Appends .txt extension to the path
                    .withRequestMethod("GET")
                    .execute()
                    .checkStatus(200)
                    .checkResponseContentType("text/plain")
                    .getResponseAsString();

            logger.info("Response from GET: {}", getResponse);

        } catch (LoginException e) {
            logger.error("Service user login failed. Check user-mapping in Apache Sling Service User Mapper.", e);
        } catch (Exception e) {
            logger.error("An unexpected error occurred while calling internal servlets.", e);
        }
    }
}

📝 What’s Happening Here?

  • You’re using AEM’s own internal servlet logic.
  • No external call is made.
  • You stay within the JVM, so it’s fast, safe, and clean.

To access complete code, please visit https://github.com/aanchalsikka/techrevelaemsite/pull/4

When to Use SlingInternalRequest — Developer Checklist

  • ✅ You need to reuse servlet logic internally from other services or jobs.
  • ✅ The servlet interacts with internal AEM APIs (like WCMCommand Servlet).
  • ✅ You need to chain servlet logic in the same request context.

When Not to Use It

  • ❌ For external integrations or when talking to third-party systems — stick to HTTP or APIs.
  • ❌ When the servlet handles file uploads or large payloads — it’s better handled through multipart HTTP requests.

One thought on “SlingInternalRequest: A Cleaner Way to Reuse OOTB AEM Servlets with Internal API Access

Leave a comment