You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2025/01/12 18:07:58 UTC

(sling-org-apache-sling-servlet-helpers) branch jakarta-2 updated: Implement new API based on Jakarta Servlet

This is an automated email from the ASF dual-hosted git repository.

cziegeler pushed a commit to branch jakarta-2
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-servlet-helpers.git


The following commit(s) were added to refs/heads/jakarta-2 by this push:
     new 6609124  Implement new API based on Jakarta Servlet
6609124 is described below

commit 66091241d607c31ccdf34f359b8cb9baa8ed0ce9
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Sun Jan 12 19:07:52 2025 +0100

    Implement new API based on Jakarta Servlet
---
 pom.xml                                            |   18 +-
 .../sling/servlethelpers/JakartaByteArrayPart.java |  125 +++
 ...package-info.java => JakartaCookieSupport.java} |   36 +-
 .../servlethelpers/JakartaResponseBodySupport.java |  110 ++
 .../servlethelpers/MockJakartaHttpSession.java     |  135 +++
 .../MockJakartaRequestDispatcherFactory.java       |   49 +
 .../servlethelpers/MockJakartaServletContext.java  |  323 ++++++
 .../MockSlingJakartaHttpServletRequest.java        | 1049 ++++++++++++++++++++
 .../MockSlingJakartaHttpServletResponse.java       |  296 ++++++
 .../apache/sling/servlethelpers/package-info.java  |    2 +-
 .../JakartaRequestInfoServlet.java                 |   98 ++
 .../internalrequests/MockServletResolver.java      |   18 +
 .../MockSlingRequestProcessor.java                 |   21 +
 13 files changed, 2270 insertions(+), 10 deletions(-)

diff --git a/pom.xml b/pom.xml
index 528f09b..deb87d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,9 +7,9 @@
     to you under the Apache License, Version 2.0 (the
     "License"); you may not use this file except in compliance
     with the License.  You may obtain a copy of the License at
-    
+
     http://www.apache.org/licenses/LICENSE-2.0
-    
+
     Unless required by applicable law or agreed to in writing,
     software distributed under the License is distributed on an
     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.servlet-helpers</artifactId>
-    <version>1.4.7-SNAPSHOT</version>
+    <version>2.0.0-SNAPSHOT</version>
     <packaging>jar</packaging>
 
     <name>Apache Sling Servlet Helpers</name>
@@ -42,7 +42,7 @@
     </scm>
 
     <properties>
-        <sling.java.version>8</sling.java.version>
+        <sling.java.version>17</sling.java.version>
         <org.ops4j.pax.exam.version>4.13.3</org.ops4j.pax.exam.version>
         <!-- To debug the pax process, override this with -D -->
         <pax.vm.options>-Xmx512M</pax.vm.options>
@@ -60,6 +60,12 @@
             <artifactId>javax.servlet-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+            <version>6.0.0</version>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
@@ -68,13 +74,13 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.25.4</version>
+            <version>3.0.0-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.engine</artifactId>
-            <version>2.6.22</version>
+            <version>3.0.0-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/src/main/java/org/apache/sling/servlethelpers/JakartaByteArrayPart.java b/src/main/java/org/apache/sling/servlethelpers/JakartaByteArrayPart.java
new file mode 100644
index 0000000..21b6a81
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/JakartaByteArrayPart.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.servlethelpers;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+
+import jakarta.servlet.http.Part;
+
+/**
+ * Simple Part implementation backed by an in-memory byte array
+ * @since 1.10.0
+ *
+ */
+public class JakartaByteArrayPart implements Part {
+
+    /**
+     * Returns a Builder instance used to create a ByteArrayPart
+     *
+     * @return a new builder instance
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    private final byte[] content;
+    private final String name;
+
+    private JakartaByteArrayPart(byte[] content, String name) {
+        if (content == null) throw new IllegalArgumentException("content may not be null");
+
+        if (name == null || name.isEmpty()) throw new IllegalArgumentException("name may not be null or empty");
+
+        this.content = content;
+        this.name = name;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        return new ByteArrayInputStream(content);
+    }
+
+    @Override
+    public String getContentType() {
+        return null;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getSubmittedFileName() {
+        return getName();
+    }
+
+    @Override
+    public long getSize() {
+        return content.length;
+    }
+
+    @Override
+    public void write(String fileName) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void delete() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getHeader(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Collection<String> getHeaders(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Collection<String> getHeaderNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    public static class Builder {
+
+        private byte[] content;
+        private String name;
+
+        public Builder withContent(byte[] content) {
+            this.content = content;
+            return this;
+        }
+
+        public Builder withName(String name) {
+            this.name = name;
+            return this;
+        }
+
+        public JakartaByteArrayPart build() {
+            return new JakartaByteArrayPart(content, name);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/package-info.java b/src/main/java/org/apache/sling/servlethelpers/JakartaCookieSupport.java
similarity index 54%
copy from src/main/java/org/apache/sling/servlethelpers/package-info.java
copy to src/main/java/org/apache/sling/servlethelpers/JakartaCookieSupport.java
index 2bcdb37..319e965 100644
--- a/src/main/java/org/apache/sling/servlethelpers/package-info.java
+++ b/src/main/java/org/apache/sling/servlethelpers/JakartaCookieSupport.java
@@ -16,8 +16,38 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.sling.servlethelpers;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import jakarta.servlet.http.Cookie;
+
 /**
- * Mock implementation of selected Servlet-related Sling APIs.
+ * Manages cookies for request and response.
+ * @since 1.10.0
  */
-@org.osgi.annotation.versioning.Version("1.9")
-package org.apache.sling.servlethelpers;
+class JakartaCookieSupport {
+
+    private Map<String, Cookie> cookies = new LinkedHashMap<String, Cookie>();
+
+    public void addCookie(Cookie cookie) {
+        cookies.put(cookie.getName(), cookie);
+    }
+
+    public Cookie getCookie(String name) {
+        return cookies.get(name);
+    }
+
+    public Cookie[] getCookies() {
+        if (cookies.isEmpty()) {
+            return null;
+        } else {
+            return cookies.values().toArray(new Cookie[cookies.size()]);
+        }
+    }
+
+    public void reset() {
+        cookies.clear();
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/JakartaResponseBodySupport.java b/src/main/java/org/apache/sling/servlethelpers/JakartaResponseBodySupport.java
new file mode 100644
index 0000000..d880a0b
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/JakartaResponseBodySupport.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.servlethelpers;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import org.apache.commons.lang3.CharEncoding;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Manage response body content.
+ * @since 1.10.0
+ */
+class JakartaResponseBodySupport {
+
+    private ByteArrayOutputStream outputStream;
+    private ServletOutputStream servletOutputStream;
+    private PrintWriter printWriter;
+
+    public JakartaResponseBodySupport() {
+        reset();
+    }
+
+    public void reset() {
+        outputStream = new ByteArrayOutputStream();
+        servletOutputStream = null;
+        printWriter = null;
+    }
+
+    public ServletOutputStream getOutputStream() {
+        if (servletOutputStream == null) {
+            servletOutputStream = new ServletOutputStream() {
+                @Override
+                public void write(int b) throws IOException {
+                    outputStream.write(b);
+                }
+
+                @Override
+                public boolean isReady() {
+                    return true;
+                }
+
+                @Override
+                public void setWriteListener(WriteListener writeListener) {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+        return servletOutputStream;
+    }
+
+    public PrintWriter getWriter(String charset) {
+        if (printWriter == null) {
+            try {
+                printWriter = new PrintWriter(new OutputStreamWriter(getOutputStream(), defaultCharset(charset)));
+            } catch (UnsupportedEncodingException ex) {
+                throw new RuntimeException("Unsupported encoding: " + defaultCharset(charset), ex);
+            }
+        }
+        return printWriter;
+    }
+
+    public byte[] getOutput() {
+        if (printWriter != null) {
+            printWriter.flush();
+        }
+        if (servletOutputStream != null) {
+            try {
+                servletOutputStream.flush();
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+        return outputStream.toByteArray();
+    }
+
+    public String getOutputAsString(String charset) {
+        try {
+            return new String(getOutput(), defaultCharset(charset));
+        } catch (UnsupportedEncodingException ex) {
+            throw new RuntimeException("Unsupported encoding: " + defaultCharset(charset), ex);
+        }
+    }
+
+    private String defaultCharset(String charset) {
+        return StringUtils.defaultString(charset, CharEncoding.UTF_8);
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockJakartaHttpSession.java b/src/main/java/org/apache/sling/servlethelpers/MockJakartaHttpSession.java
new file mode 100644
index 0000000..3c95670
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockJakartaHttpSession.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.servlethelpers;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.http.HttpSession;
+import org.apache.commons.collections4.IteratorUtils;
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * Mock {@link HttpSession} implementation.
+ * @since 1.10.0
+ */
+@ConsumerType
+public class MockJakartaHttpSession implements HttpSession {
+
+    private final ServletContext servletContext;
+    private final Map<String, Object> attributeMap = new HashMap<String, Object>();
+    private final String sessionID = UUID.randomUUID().toString();
+    private final long creationTime = System.currentTimeMillis();
+    private boolean invalidated = false;
+    private boolean isNew = true;
+    private int maxActiveInterval = 1800;
+
+    public MockJakartaHttpSession() {
+        this.servletContext = newMockJakartaServletContext();
+    }
+
+    protected MockJakartaServletContext newMockJakartaServletContext() {
+        return new MockJakartaServletContext();
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return this.servletContext;
+    }
+
+    @Override
+    public Object getAttribute(final String name) {
+        checkInvalidatedState();
+        return this.attributeMap.get(name);
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        checkInvalidatedState();
+        return IteratorUtils.asEnumeration(this.attributeMap.keySet().iterator());
+    }
+
+    @Override
+    public String getId() {
+        return this.sessionID;
+    }
+
+    @Override
+    public long getCreationTime() {
+        checkInvalidatedState();
+        return this.creationTime;
+    }
+
+    @Override
+    public void removeAttribute(final String name) {
+        checkInvalidatedState();
+        this.attributeMap.remove(name);
+    }
+
+    @Override
+    public void setAttribute(final String name, final Object value) {
+        checkInvalidatedState();
+        this.attributeMap.put(name, value);
+    }
+
+    @Override
+    public void invalidate() {
+        checkInvalidatedState();
+        this.invalidated = true;
+    }
+
+    private void checkInvalidatedState() {
+        if (invalidated) {
+            throw new IllegalStateException("Session is already invalidated.");
+        }
+    }
+
+    public boolean isInvalidated() {
+        return invalidated;
+    }
+
+    @Override
+    public boolean isNew() {
+        checkInvalidatedState();
+        return isNew;
+    }
+
+    public void setNew(boolean isNew) {
+        this.isNew = isNew;
+    }
+
+    @Override
+    public long getLastAccessedTime() {
+        checkInvalidatedState();
+        return creationTime;
+    }
+
+    @Override
+    public int getMaxInactiveInterval() {
+        return maxActiveInterval;
+    }
+
+    @Override
+    public void setMaxInactiveInterval(final int interval) {
+        this.maxActiveInterval = interval;
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockJakartaRequestDispatcherFactory.java b/src/main/java/org/apache/sling/servlethelpers/MockJakartaRequestDispatcherFactory.java
new file mode 100644
index 0000000..c5bae1e
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockJakartaRequestDispatcherFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.servlethelpers;
+
+import jakarta.servlet.RequestDispatcher;
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.resource.Resource;
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * Interface to create a mock {@link RequestDispatcher} when calling the getRequestDispatcher methods
+ * on {@link MockSlingJakartaHttpServletRequest} instances.
+ * @since 1.10.0
+ */
+@ConsumerType
+public interface MockJakartaRequestDispatcherFactory {
+
+    /**
+     * Get request dispatcher for given path.
+     * @param path Path
+     * @param options Options. Null if no options are provided.
+     * @return Request dispatcher
+     */
+    RequestDispatcher getRequestDispatcher(String path, RequestDispatcherOptions options);
+
+    /**
+     * Get request dispatcher for given resource.
+     * @param resource Resource
+     * @param options Options. Null if no options are provided.
+     * @return Request dispatcher
+     */
+    RequestDispatcher getRequestDispatcher(Resource resource, RequestDispatcherOptions options);
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockJakartaServletContext.java b/src/main/java/org/apache/sling/servlethelpers/MockJakartaServletContext.java
new file mode 100644
index 0000000..4a0fae5
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockJakartaServletContext.java
@@ -0,0 +1,323 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.servlethelpers;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.Map;
+import java.util.Set;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterRegistration;
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.Servlet;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRegistration;
+import jakarta.servlet.ServletRegistration.Dynamic;
+import jakarta.servlet.SessionCookieConfig;
+import jakarta.servlet.SessionTrackingMode;
+import jakarta.servlet.descriptor.JspConfigDescriptor;
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * Mock {@link ServletContext} implementation.
+ * @since 1.10.0
+ */
+@ConsumerType
+public class MockJakartaServletContext implements ServletContext {
+
+    @Override
+    public String getMimeType(final String file) {
+        return "application/octet-stream";
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public Object getAttribute(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServletContext getContext(final String uriPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getContextPath() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getInitParameter(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getMajorVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getMinorVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestDispatcher getNamedDispatcher(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRealPath(final String pPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public URL getResource(final String pPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public InputStream getResourceAsStream(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<String> getResourcePaths(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getServerInfo() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getServletContextName() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void log(final String msg) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void log(final String msg, final Throwable throwable) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeAttribute(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setAttribute(final String name, final Object object) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getEffectiveMajorVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getEffectiveMinorVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean setInitParameter(final String name, final String value) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Dynamic addServlet(final String servletName, final String className) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Dynamic addServlet(final String servletName, final Servlet servlet) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Dynamic addServlet(final String servletName, final Class<? extends Servlet> servletClass) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends Servlet> T createServlet(final Class<T> clazz) throws ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServletRegistration getServletRegistration(final String servletName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, ? extends ServletRegistration> getServletRegistrations() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(final String filterName, final String className) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(final String filterName, final Filter filter) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(final String filterName, final Class<? extends Filter> filterClass) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends Filter> T createFilter(final Class<T> clazz) throws ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FilterRegistration getFilterRegistration(final String filterName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SessionCookieConfig getSessionCookieConfig() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setSessionTrackingModes(final Set<SessionTrackingMode> sessionTrackingModes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void addListener(final String pClassName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends EventListener> void addListener(final T listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void addListener(final Class<? extends EventListener> listenerClass) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends EventListener> T createListener(final Class<T> clazz) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public JspConfigDescriptor getJspConfigDescriptor() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ClassLoader getClassLoader() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void declareRoles(final String... roleNames) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getVirtualServerName() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Dynamic addJspFile(String servletName, String jspFile) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRequestCharacterEncoding() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getResponseCharacterEncoding() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getSessionTimeout() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setRequestCharacterEncoding(String encoding) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setResponseCharacterEncoding(String encoding) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setSessionTimeout(int sessionTimeout) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockSlingJakartaHttpServletRequest.java b/src/main/java/org/apache/sling/servlethelpers/MockSlingJakartaHttpServletRequest.java
new file mode 100644
index 0000000..517b0d1
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockSlingJakartaHttpServletRequest.java
@@ -0,0 +1,1049 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.servlethelpers;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.ListResourceBundle;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.ResourceBundle;
+
+import jakarta.servlet.AsyncContext;
+import jakarta.servlet.DispatcherType;
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.ServletConnection;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import jakarta.servlet.http.HttpUpgradeHandler;
+import jakarta.servlet.http.Part;
+import org.apache.commons.collections4.IteratorUtils;
+import org.apache.commons.lang3.CharEncoding;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingJakartaHttpServletRequest;
+import org.apache.sling.api.adapter.SlingAdaptable;
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.request.RequestParameterMap;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.request.RequestProgressTracker;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.servlets.HttpConstants;
+import org.osgi.annotation.versioning.ConsumerType;
+
+import static org.apache.sling.servlethelpers.MockSlingHttpServletResponse.CHARSET_SEPARATOR;
+
+/**
+ * Mock {@link SlingJakartaHttpServletRequest} implementation.
+ * @since 1.10.0
+ */
+@ConsumerType
+public class MockSlingJakartaHttpServletRequest extends SlingAdaptable implements SlingJakartaHttpServletRequest {
+
+    private final ResourceResolver resourceResolver;
+    private final RequestPathInfo requestPathInfo;
+    private Map<String, Object> attributeMap = new HashMap<>();
+    private Map<String, MockRequestParameter[]> parameterMap = new LinkedHashMap<>();
+    private HttpSession session;
+    private Resource resource;
+    private String authType;
+    private String contextPath = "";
+    private String queryString;
+    private String scheme = "http";
+    private String serverName = "localhost";
+    private int serverPort = 80;
+    private String servletPath = StringUtils.EMPTY;
+    private String pathInfo = null;
+    private String method = HttpConstants.METHOD_GET;
+    private final HeaderSupport headerSupport = new HeaderSupport();
+    private final JakartaCookieSupport cookieSupport = new JakartaCookieSupport();
+    private String contentType;
+    private String characterEncoding;
+    private byte[] content;
+    private String remoteUser;
+    private String remoteAddr;
+    private String remoteHost;
+    private int remotePort;
+    private Locale locale = Locale.US;
+    private boolean getInputStreamCalled;
+    private boolean getReaderCalled;
+    private List<Part> parts = new ArrayList<>();
+
+    private MockJakartaRequestDispatcherFactory requestDispatcherFactory;
+    private String responseContentType;
+
+    protected static final ResourceBundle EMPTY_RESOURCE_BUNDLE = new ListResourceBundle() {
+        @Override
+        protected Object[][] getContents() {
+            return new Object[0][0];
+        }
+    };
+
+    /**
+     * @param resourceResolver Resource resolver
+     */
+    public MockSlingJakartaHttpServletRequest(ResourceResolver resourceResolver) {
+        this.resourceResolver = resourceResolver;
+        this.requestPathInfo = newMockRequestPathInfo();
+    }
+
+    protected MockJakartaHttpSession newMockJakartaHttpSession() {
+        return new MockJakartaHttpSession();
+    }
+
+    protected MockRequestPathInfo newMockRequestPathInfo() {
+        return new MockRequestPathInfo(this.resourceResolver);
+    }
+
+    @Override
+    public ResourceResolver getResourceResolver() {
+        return this.resourceResolver;
+    }
+
+    @Override
+    public HttpSession getSession() {
+        return getSession(true);
+    }
+
+    @Override
+    public HttpSession getSession(boolean create) {
+        if (this.session == null && create) {
+            this.session = newMockJakartaHttpSession();
+        }
+        return this.session;
+    }
+
+    @Override
+    public RequestPathInfo getRequestPathInfo() {
+        return this.requestPathInfo;
+    }
+
+    @Override
+    public Object getAttribute(String name) {
+        return this.attributeMap.get(name);
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        return IteratorUtils.asEnumeration(this.attributeMap.keySet().iterator());
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+        this.attributeMap.remove(name);
+    }
+
+    @Override
+    public void setAttribute(String name, Object object) {
+        this.attributeMap.put(name, object);
+    }
+
+    @Override
+    public Resource getResource() {
+        return this.resource;
+    }
+
+    public void setResource(Resource resource) {
+        this.resource = resource;
+    }
+
+    @Override
+    public String getParameter(String name) {
+        MockRequestParameter[] params = this.parameterMap.get(name);
+        if (params != null && params.length > 0) {
+            return params[0].getString();
+        }
+        return null;
+    }
+
+    @Override
+    public Map<String, String[]> getParameterMap() {
+        LinkedHashMap<String, String[]> result = new LinkedHashMap<>();
+        for (Entry<String, MockRequestParameter[]> entry : this.parameterMap.entrySet()) {
+            MockRequestParameter[] values = entry.getValue();
+            String[] resultValues = new String[values.length];
+            for (int i = 0; i < values.length; i++) {
+                resultValues[i] = values[i].getString();
+            }
+            result.put(entry.getKey(), resultValues);
+        }
+
+        return result;
+    }
+
+    @Override
+    public Enumeration<String> getParameterNames() {
+        return IteratorUtils.asEnumeration(this.parameterMap.keySet().iterator());
+    }
+
+    @Override
+    public String[] getParameterValues(String name) { // NOPMD
+        MockRequestParameter[] param = this.parameterMap.get(name);
+        if (param != null) {
+            String[] values = new String[param.length];
+            for (int i = 0; i < param.length; i++) {
+                values[i] = param[i].getString();
+            }
+            return values;
+        }
+        return null; // NOPMD
+    }
+
+    /**
+     * @param parameterMap Map of parameters
+     */
+    public void setParameterMap(Map<String, Object> parameterMap) {
+        this.parameterMap.clear();
+        for (Map.Entry<String, Object> entry : parameterMap.entrySet()) {
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            if (value instanceof String[]) {
+                String[] array = (String[]) value;
+                MockRequestParameter[] values = new MockRequestParameter[array.length];
+                for (int i = 0; i < array.length; i++) {
+                    values[i] = new MockRequestParameter(key, array[i]);
+                }
+                this.parameterMap.put(key, values);
+            } else if (value instanceof MockRequestParameter[]) {
+                this.parameterMap.put(key, (MockRequestParameter[]) value);
+            } else if (value != null) {
+                this.addRequestParameter(key, value.toString());
+            } else {
+                this.parameterMap.put(key, null);
+            }
+        }
+        try {
+            this.queryString = formatQueryString(this.parameterMap);
+        } catch (UnsupportedEncodingException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private static String formatQueryString(Map<String, MockRequestParameter[]> map)
+            throws UnsupportedEncodingException {
+        StringBuilder querystring = new StringBuilder();
+        for (Map.Entry<String, MockRequestParameter[]> entry : map.entrySet()) {
+            if (entry.getValue() != null) {
+                formatQueryStringParameter(querystring, entry);
+            }
+        }
+        return querystring.length() > 0 ? querystring.toString() : null;
+    }
+
+    private static void formatQueryStringParameter(
+            StringBuilder querystring, Map.Entry<String, MockRequestParameter[]> entry)
+            throws UnsupportedEncodingException {
+        for (MockRequestParameter value : entry.getValue()) {
+            if (querystring.length() != 0) {
+                querystring.append('&');
+            }
+            querystring.append(URLEncoder.encode(entry.getKey(), CharEncoding.UTF_8));
+            querystring.append('=');
+            if (value.getString() != null) {
+                querystring.append(URLEncoder.encode(value.getString(), CharEncoding.UTF_8));
+            }
+        }
+    }
+
+    @Override
+    public Locale getLocale() {
+        return locale;
+    }
+
+    /**
+     * @param loc Request locale
+     */
+    public void setLocale(Locale loc) {
+        this.locale = loc;
+    }
+
+    @Override
+    public String getContextPath() {
+        return this.contextPath;
+    }
+
+    /**
+     * @param contextPath Webapp context path
+     */
+    public void setContextPath(String contextPath) {
+        this.contextPath = contextPath;
+    }
+
+    /**
+     * @param queryString Query string (with proper URL encoding)
+     */
+    public void setQueryString(String queryString) {
+        this.queryString = queryString;
+        try {
+            parseQueryString(this.parameterMap, this.queryString);
+        } catch (UnsupportedEncodingException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private void parseQueryString(Map<String, MockRequestParameter[]> map, String query)
+            throws UnsupportedEncodingException {
+        Map<String, List<String>> queryPairs = new LinkedHashMap<>();
+        String[] pairs = query.split("&");
+        for (String pair : pairs) {
+            int idx = pair.indexOf('=');
+            String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), CharEncoding.UTF_8) : pair;
+            if (!queryPairs.containsKey(key)) {
+                queryPairs.put(key, new ArrayList<String>());
+            }
+            String value = idx > 0 && pair.length() > idx + 1
+                    ? URLDecoder.decode(pair.substring(idx + 1), CharEncoding.UTF_8)
+                    : null;
+            queryPairs.get(key).add(value);
+        }
+        map.clear();
+        for (Map.Entry<String, List<String>> entry : queryPairs.entrySet()) {
+            List<String> valueList = entry.getValue();
+            int numEntries = valueList.size();
+            MockRequestParameter[] values = new MockRequestParameter[numEntries];
+            for (int i = 0; i < numEntries; i++) {
+                values[i] = new MockRequestParameter(entry.getKey(), valueList.get(i));
+            }
+            map.put(entry.getKey(), values);
+        }
+    }
+
+    @Override
+    public String getQueryString() {
+        return this.queryString;
+    }
+
+    @Override
+    public String getScheme() {
+        return this.scheme;
+    }
+
+    public void setScheme(String scheme) {
+        this.scheme = scheme;
+    }
+
+    @Override
+    public String getServerName() {
+        return this.serverName;
+    }
+
+    public void setServerName(String serverName) {
+        this.serverName = serverName;
+    }
+
+    @Override
+    public int getServerPort() {
+        return this.serverPort;
+    }
+
+    public void setServerPort(int serverPort) {
+        this.serverPort = serverPort;
+    }
+
+    @Override
+    public boolean isSecure() {
+        return StringUtils.equals("https", getScheme());
+    }
+
+    @Override
+    public String getMethod() {
+        return this.method;
+    }
+
+    public void setMethod(String method) {
+        this.method = method;
+    }
+
+    @Override
+    public long getDateHeader(String name) {
+        return headerSupport.getDateHeader(name);
+    }
+
+    @Override
+    public String getHeader(String name) {
+        return headerSupport.getHeader(name);
+    }
+
+    @Override
+    public Enumeration<String> getHeaderNames() {
+        return HeaderSupport.toEnumeration(headerSupport.getHeaderNames());
+    }
+
+    @Override
+    public Enumeration<String> getHeaders(String name) {
+        return HeaderSupport.toEnumeration(headerSupport.getHeaders(name));
+    }
+
+    @Override
+    public int getIntHeader(String name) {
+        return headerSupport.getIntHeader(name);
+    }
+
+    /**
+     * Add header, keep existing ones with same name.
+     *
+     * @param name  Header name
+     * @param value Header value
+     */
+    public void addHeader(String name, String value) {
+        headerSupport.addHeader(name, value);
+    }
+
+    /**
+     * Add header, keep existing ones with same name.
+     *
+     * @param name  Header name
+     * @param value Header value
+     */
+    public void addIntHeader(String name, int value) {
+        headerSupport.addIntHeader(name, value);
+    }
+
+    /**
+     * Add header, keep existing ones with same name.
+     *
+     * @param name Header name
+     * @param date Header value
+     */
+    public void addDateHeader(String name, long date) {
+        headerSupport.addDateHeader(name, date);
+    }
+
+    /**
+     * Set header, overwrite existing ones with same name.
+     *
+     * @param name  Header name
+     * @param value Header value
+     */
+    public void setHeader(String name, String value) {
+        headerSupport.setHeader(name, value);
+    }
+
+    /**
+     * Set header, overwrite existing ones with same name.
+     *
+     * @param name  Header name
+     * @param value Header value
+     */
+    public void setIntHeader(String name, int value) {
+        headerSupport.setIntHeader(name, value);
+    }
+
+    /**
+     * Set header, overwrite existing ones with same name.
+     *
+     * @param name Header name
+     * @param date Header value
+     */
+    public void setDateHeader(String name, long date) {
+        headerSupport.setDateHeader(name, date);
+    }
+
+    @Override
+    public Cookie getCookie(String name) {
+        return cookieSupport.getCookie(name);
+    }
+
+    @Override
+    public Cookie[] getCookies() {
+        return cookieSupport.getCookies();
+    }
+
+    /**
+     * Set cookie
+     *
+     * @param cookie Cookie
+     */
+    public void addCookie(Cookie cookie) {
+        cookieSupport.addCookie(cookie);
+    }
+
+    @Override
+    public ResourceBundle getResourceBundle(Locale locale) {
+        return getResourceBundle(null, locale);
+    }
+
+    @Override
+    public ResourceBundle getResourceBundle(String baseName, Locale locale) {
+        return EMPTY_RESOURCE_BUNDLE;
+    }
+
+    @Override
+    public RequestParameter getRequestParameter(String name) {
+        MockRequestParameter[] params = this.parameterMap.get(name);
+        if (params != null && params.length > 0) {
+            return params[0];
+        }
+        return null;
+    }
+
+    @Override
+    public RequestParameterMap getRequestParameterMap() {
+        MockRequestParameterMap map = new MockRequestParameterMap();
+        for (Map.Entry<String, String[]> entry : getParameterMap().entrySet()) {
+            map.put(entry.getKey(), getRequestParameters(entry.getKey()));
+        }
+        return map;
+    }
+
+    @Override
+    public RequestParameter[] getRequestParameters(String name) {
+        return this.parameterMap.get(name);
+    }
+
+    // part of Sling API 2.7
+    public List<RequestParameter> getRequestParameterList() {
+        List<RequestParameter> params = new ArrayList<>();
+        for (RequestParameter[] requestParameters : getRequestParameterMap().values()) {
+            params.addAll(Arrays.asList(requestParameters));
+        }
+        return params;
+    }
+
+    /**
+     * Add a request parameter that consists of a simple name/value pair. This
+     * emulates a simple form field.
+     *
+     * @param name  field name
+     * @param value field value
+     */
+    public void addRequestParameter(String name, String value) {
+        MockRequestParameter mockRequestParameter = new MockRequestParameter(name, value);
+        addMockRequestParameter(name, mockRequestParameter);
+    }
+
+    /**
+     * Add a request parameter that emulates a file upload field.
+     *
+     * @param name        field name
+     * @param content     file content
+     * @param contentType mime type of content in the field
+     */
+    public void addRequestParameter(String name, byte[] content, String contentType) {
+        MockRequestParameter mockRequestParameter = new MockRequestParameter(name, content, contentType);
+        addMockRequestParameter(name, mockRequestParameter);
+    }
+
+    /**
+     * Add a request parameter that emulates a file upload field with a filename
+     * associated with it.
+     *
+     * @param name        field name
+     * @param content     file content
+     * @param contentType mime type of content in the field
+     * @param filename    filename associated with content
+     */
+    public void addRequestParameter(String name, byte[] content, String contentType, String filename) {
+        MockRequestParameter mockRequestParameter = new MockRequestParameter(name, content, contentType, filename);
+        addMockRequestParameter(name, mockRequestParameter);
+    }
+
+    private void addMockRequestParameter(String name, MockRequestParameter mockRequestParameter) {
+        if (this.parameterMap.containsKey(name)) {
+            List<MockRequestParameter> list = new ArrayList<>(Arrays.asList(this.parameterMap.get(name)));
+            list.add(mockRequestParameter);
+            this.parameterMap.put(name, list.toArray(new MockRequestParameter[0]));
+        } else {
+            this.parameterMap.put(name, new MockRequestParameter[] {mockRequestParameter});
+        }
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+        return this.characterEncoding;
+    }
+
+    @Override
+    public void setCharacterEncoding(String charset) {
+        this.characterEncoding = charset;
+    }
+
+    @Override
+    public String getContentType() {
+        if (this.contentType == null) {
+            return null;
+        } else {
+            return this.contentType
+                    + (StringUtils.isNotBlank(characterEncoding) ? CHARSET_SEPARATOR + characterEncoding : "");
+        }
+    }
+
+    public void setContentType(String type) {
+        this.contentType = type;
+        if (StringUtils.contains(this.contentType, CHARSET_SEPARATOR)) {
+            this.characterEncoding = StringUtils.substringAfter(this.contentType, CHARSET_SEPARATOR);
+            this.contentType = StringUtils.substringBefore(this.contentType, CHARSET_SEPARATOR);
+        }
+    }
+
+    @Override
+    public ServletInputStream getInputStream() {
+        if (getReaderCalled) {
+            throw new IllegalStateException();
+        }
+        getInputStreamCalled = true;
+        return new ServletInputStream() {
+            private final InputStream is =
+                    content == null ? new ByteArrayInputStream(new byte[0]) : new ByteArrayInputStream(content);
+
+            @Override
+            public int read() throws IOException {
+                return is.read();
+            }
+
+            @Override
+            public boolean isReady() {
+                return true;
+            }
+
+            @Override
+            public boolean isFinished() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener) {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    @Override
+    public int getContentLength() {
+        if (content == null) {
+            return 0;
+        }
+        return content.length;
+    }
+
+    public void setContent(byte[] content) {
+        this.content = content;
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path) {
+        if (requestDispatcherFactory == null) {
+            throw new IllegalStateException(
+                    "Please provdide a MockRequestDispatcherFactory (setRequestDispatcherFactory).");
+        }
+        return requestDispatcherFactory.getRequestDispatcher(path, null);
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path, RequestDispatcherOptions options) {
+        if (requestDispatcherFactory == null) {
+            throw new IllegalStateException(
+                    "Please provdide a MockRequestDispatcherFactory (setRequestDispatcherFactory).");
+        }
+        return requestDispatcherFactory.getRequestDispatcher(path, options);
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(Resource resource) {
+        if (requestDispatcherFactory == null) {
+            throw new IllegalStateException(
+                    "Please provdide a MockRequestDispatcherFactory (setRequestDispatcherFactory).");
+        }
+        return requestDispatcherFactory.getRequestDispatcher(resource, null);
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(Resource resource, RequestDispatcherOptions options) {
+        if (requestDispatcherFactory == null) {
+            throw new IllegalStateException(
+                    "Please provdide a MockRequestDispatcherFactory (setRequestDispatcherFactory).");
+        }
+        return requestDispatcherFactory.getRequestDispatcher(resource, options);
+    }
+
+    public void setRequestDispatcherFactory(MockJakartaRequestDispatcherFactory requestDispatcherFactory) {
+        this.requestDispatcherFactory = requestDispatcherFactory;
+    }
+
+    @Override
+    public String getRemoteUser() {
+        return remoteUser;
+    }
+
+    public void setRemoteUser(String remoteUser) {
+        this.remoteUser = remoteUser;
+    }
+
+    @Override
+    public String getRemoteAddr() {
+        return remoteAddr;
+    }
+
+    public void setRemoteAddr(String remoteAddr) {
+        this.remoteAddr = remoteAddr;
+    }
+
+    @Override
+    public String getRemoteHost() {
+        return remoteHost;
+    }
+
+    public void setRemoteHost(String remoteHost) {
+        this.remoteHost = remoteHost;
+    }
+
+    @Override
+    public int getRemotePort() {
+        return remotePort;
+    }
+
+    public void setRemotePort(int remotePort) {
+        this.remotePort = remotePort;
+    }
+
+    @Override
+    public String getServletPath() {
+        return this.servletPath;
+    }
+
+    public void setServletPath(String servletPath) {
+        this.servletPath = servletPath;
+    }
+
+    @Override
+    public String getPathInfo() {
+        if (this.pathInfo != null) {
+            return this.pathInfo;
+        }
+
+        RequestPathInfo requestPathInfo = this.getRequestPathInfo();
+
+        if (StringUtils.isEmpty(requestPathInfo.getResourcePath())) {
+            return null;
+        }
+
+        StringBuilder pathInfo = new StringBuilder();
+
+        pathInfo.append(requestPathInfo.getResourcePath());
+
+        if (StringUtils.isNotEmpty(requestPathInfo.getSelectorString())) {
+            pathInfo.append('.');
+            pathInfo.append(requestPathInfo.getSelectorString());
+        }
+
+        if (StringUtils.isNotEmpty(requestPathInfo.getExtension())) {
+            pathInfo.append('.');
+            pathInfo.append(requestPathInfo.getExtension());
+        }
+
+        if (StringUtils.isNotEmpty(requestPathInfo.getSuffix())) {
+            pathInfo.append(requestPathInfo.getSuffix());
+        }
+
+        return pathInfo.toString();
+    }
+
+    public void setPathInfo(String pathInfo) {
+        this.pathInfo = pathInfo;
+    }
+
+    @Override
+    public String getRequestURI() {
+        StringBuilder requestUri = new StringBuilder();
+
+        if (StringUtils.isNotEmpty(this.getServletPath())) {
+            requestUri.append(this.getServletPath());
+        }
+
+        if (StringUtils.isNotEmpty(this.getPathInfo())) {
+            requestUri.append(this.getPathInfo());
+        }
+
+        if (StringUtils.isEmpty(requestUri)) {
+            return "/";
+        } else {
+            return requestUri.toString();
+        }
+    }
+
+    @Override
+    public StringBuffer getRequestURL() {
+        StringBuffer requestUrl = new StringBuffer();
+
+        requestUrl.append(this.getScheme());
+        requestUrl.append("://");
+        requestUrl.append(getServerName());
+        if ((StringUtils.equals(this.getScheme(), "http") && this.getServerPort() != 80)
+                || (StringUtils.equals(this.getScheme(), "https") && this.getServerPort() != 443)) {
+            requestUrl.append(':');
+            requestUrl.append(getServerPort());
+        }
+        requestUrl.append(getRequestURI());
+
+        return requestUrl;
+    }
+
+    @Override
+    public String getAuthType() {
+        return this.authType;
+    }
+
+    public void setAuthType(String authType) {
+        this.authType = authType;
+    }
+
+    @Override
+    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        return AdaptableUtil.adaptToWithoutCaching(this, type);
+    }
+
+    @Override
+    public String getResponseContentType() {
+        return responseContentType;
+    }
+
+    public void setResponseContentType(String responseContentType) {
+        this.responseContentType = responseContentType;
+    }
+
+    @Override
+    public Enumeration<String> getResponseContentTypes() {
+        return Collections.enumeration(Collections.singleton(responseContentType));
+    }
+
+    @Override
+    public BufferedReader getReader() {
+        if (getInputStreamCalled) {
+            throw new IllegalStateException();
+        }
+        getReaderCalled = true;
+        if (this.content == null) {
+            return new BufferedReader(new StringReader(""));
+        } else {
+            String content;
+            try {
+                if (characterEncoding == null) {
+                    content = new String(this.content, Charset.defaultCharset());
+                } else {
+                    content = new String(this.content, characterEncoding);
+                }
+            } catch (UnsupportedEncodingException e) {
+                content = new String(this.content, Charset.defaultCharset());
+            }
+            return new BufferedReader(new StringReader(content));
+        }
+    }
+
+    @Override
+    public RequestProgressTracker getRequestProgressTracker() {
+        return new MockRequestProgressTracker();
+    }
+
+    public void addPart(Part part) {
+        if (part == null) throw new IllegalArgumentException("part may not be null");
+        this.parts.add(part);
+    }
+
+    @Override
+    public Collection<Part> getParts() {
+        return parts;
+    }
+
+    @Override
+    public Part getPart(String name) {
+        return parts.stream().filter(p -> p.getName().equals(name)).findFirst().orElse(null);
+    }
+
+    @Override
+    public Principal getUserPrincipal() {
+        Principal principal = null;
+        // always return null for anonymous user
+        final String userid = getRemoteUser();
+        if (userid != null) {
+            ResourceResolver rr = getResourceResolver();
+            if (rr != null) {
+                principal = rr.adaptTo(Principal.class);
+            }
+
+            if (principal == null) {
+                // fallback to the userid
+                principal = () -> userid;
+            }
+        }
+
+        return principal;
+    }
+
+    // --- unsupported operations ---
+
+    @Override
+    public String getPathTranslated() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRequestedSessionId() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromCookie() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromURL() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRequestedSessionIdValid() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isUserInRole(String role) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getLocalAddr() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getLocalName() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getLocalPort() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<Locale> getLocales() {
+        return Collections.enumeration(Collections.singleton(getLocale()));
+    }
+
+    @Override
+    public String getProtocol() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean authenticate(HttpServletResponse response) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void login(String pUsername, String password) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void logout() throws ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AsyncContext startAsync() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isAsyncStarted() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isAsyncSupported() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AsyncContext getAsyncContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public DispatcherType getDispatcherType() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String changeSessionId() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long getContentLengthLong() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getProtocolRequestId() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRequestId() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServletConnection getServletConnection() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockSlingJakartaHttpServletResponse.java b/src/main/java/org/apache/sling/servlethelpers/MockSlingJakartaHttpServletResponse.java
new file mode 100644
index 0000000..96d4c9f
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockSlingJakartaHttpServletResponse.java
@@ -0,0 +1,296 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.servlethelpers;
+
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Locale;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingJakartaHttpServletResponse;
+import org.apache.sling.api.adapter.SlingAdaptable;
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * Mock {@link SlingJakartaHttpServletResponse} implementation.
+ * @since 1.10.0
+ */
+@ConsumerType
+public class MockSlingJakartaHttpServletResponse extends SlingAdaptable implements SlingJakartaHttpServletResponse {
+
+    static final String CHARSET_SEPARATOR = ";charset=";
+
+    private String contentType;
+    private String characterEncoding;
+    private int contentLength;
+    private int status = HttpServletResponse.SC_OK;
+    private String statusMessage;
+    private int bufferSize = 1024 * 8;
+    private boolean isCommitted;
+    private Locale locale = Locale.US;
+    private final HeaderSupport headerSupport = new HeaderSupport();
+    private final JakartaResponseBodySupport bodySupport = new JakartaResponseBodySupport();
+    private final JakartaCookieSupport cookieSupport = new JakartaCookieSupport();
+
+    @Override
+    public String getContentType() {
+        if (this.contentType == null) {
+            return null;
+        } else {
+            return this.contentType
+                    + (StringUtils.isNotBlank(characterEncoding) ? CHARSET_SEPARATOR + characterEncoding : "");
+        }
+    }
+
+    @Override
+    public void setContentType(String type) {
+        this.contentType = type;
+        if (StringUtils.contains(this.contentType, CHARSET_SEPARATOR)) {
+            this.characterEncoding = StringUtils.substringAfter(this.contentType, CHARSET_SEPARATOR);
+            this.contentType = StringUtils.substringBefore(this.contentType, CHARSET_SEPARATOR);
+        }
+    }
+
+    @Override
+    public void setCharacterEncoding(String charset) {
+        this.characterEncoding = charset;
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+        return this.characterEncoding;
+    }
+
+    @Override
+    public void setContentLength(int len) {
+        this.contentLength = len;
+    }
+
+    public int getContentLength() {
+        return this.contentLength;
+    }
+
+    @Override
+    public void setStatus(int sc) {
+        this.status = sc;
+    }
+
+    @Override
+    public int getStatus() {
+        return this.status;
+    }
+
+    @Override
+    public void sendError(int sc, String msg) {
+        setStatus(sc);
+        this.statusMessage = msg;
+    }
+
+    @Override
+    public void sendError(int sc) {
+        setStatus(sc);
+    }
+
+    @Override
+    public void sendRedirect(String location) {
+        setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+        setHeader("Location", location);
+    }
+
+    @Override
+    public void addHeader(String name, String value) {
+        headerSupport.addHeader(name, value);
+    }
+
+    @Override
+    public void addIntHeader(String name, int value) {
+        headerSupport.addIntHeader(name, value);
+    }
+
+    @Override
+    public void addDateHeader(String name, long date) {
+        headerSupport.addDateHeader(name, date);
+    }
+
+    @Override
+    public void setHeader(String name, String value) {
+        headerSupport.setHeader(name, value);
+    }
+
+    @Override
+    public void setIntHeader(String name, int value) {
+        headerSupport.setIntHeader(name, value);
+    }
+
+    @Override
+    public void setDateHeader(String name, long date) {
+        headerSupport.setDateHeader(name, date);
+    }
+
+    @Override
+    public boolean containsHeader(String name) {
+        return headerSupport.containsHeader(name);
+    }
+
+    @Override
+    public String getHeader(String name) {
+        return headerSupport.getHeader(name);
+    }
+
+    @Override
+    public Collection<String> getHeaders(String name) {
+        return headerSupport.getHeaders(name);
+    }
+
+    @Override
+    public Collection<String> getHeaderNames() {
+        return headerSupport.getHeaderNames();
+    }
+
+    @Override
+    public PrintWriter getWriter() {
+        return bodySupport.getWriter(getCharacterEncoding());
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() {
+        return bodySupport.getOutputStream();
+    }
+
+    @Override
+    public void reset() {
+        if (isCommitted()) {
+            throw new IllegalStateException("Response already committed.");
+        }
+        bodySupport.reset();
+        headerSupport.reset();
+        cookieSupport.reset();
+        status = HttpServletResponse.SC_OK;
+        contentLength = 0;
+    }
+
+    @Override
+    public void resetBuffer() {
+        if (isCommitted()) {
+            throw new IllegalStateException("Response already committed.");
+        }
+        bodySupport.reset();
+    }
+
+    @Override
+    public int getBufferSize() {
+        return this.bufferSize;
+    }
+
+    @Override
+    public void setBufferSize(int size) {
+        this.bufferSize = size;
+    }
+
+    @Override
+    public void flushBuffer() {
+        isCommitted = true;
+    }
+
+    @Override
+    public boolean isCommitted() {
+        return isCommitted;
+    }
+
+    public byte[] getOutput() {
+        return bodySupport.getOutput();
+    }
+
+    public String getOutputAsString() {
+        return bodySupport.getOutputAsString(getCharacterEncoding());
+    }
+
+    @Override
+    public void addCookie(Cookie cookie) {
+        cookieSupport.addCookie(cookie);
+    }
+
+    /**
+     * Get cookie
+     * @param name Cookie name
+     * @return Cookie or null
+     */
+    public Cookie getCookie(String name) {
+        return cookieSupport.getCookie(name);
+    }
+
+    /**
+     * Get cookies
+     * @return Cookies array or null if no cookie defined
+     */
+    public Cookie[] getCookies() {
+        return cookieSupport.getCookies();
+    }
+
+    @Override
+    public Locale getLocale() {
+        return locale;
+    }
+
+    @Override
+    public void setLocale(Locale loc) {
+        this.locale = loc;
+    }
+
+    /**
+     * @return status message that was set using {@link #setStatus(int, String)} or {@link #sendError(int, String)}
+     */
+    public String getStatusMessage() {
+        return statusMessage;
+    }
+
+    /**
+     * @deprecated As of package version 1.6, use {@link #getStatusMessage()} instead.
+     *
+     * @return status message that was set using {@link #setStatus(int, String)} or {@link #sendError(int, String)}
+     */
+    @Deprecated
+    public String geStatusMessage() {
+        return this.getStatusMessage();
+    }
+
+    @Override
+    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        return AdaptableUtil.adaptToWithoutCaching(this, type);
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public String encodeRedirectURL(String url) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String encodeURL(String url) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setContentLengthLong(long len) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/package-info.java b/src/main/java/org/apache/sling/servlethelpers/package-info.java
index 2bcdb37..88a4a2a 100644
--- a/src/main/java/org/apache/sling/servlethelpers/package-info.java
+++ b/src/main/java/org/apache/sling/servlethelpers/package-info.java
@@ -19,5 +19,5 @@
 /**
  * Mock implementation of selected Servlet-related Sling APIs.
  */
-@org.osgi.annotation.versioning.Version("1.9")
+@org.osgi.annotation.versioning.Version("1.10")
 package org.apache.sling.servlethelpers;
diff --git a/src/test/java/org/apache/sling/servlethelpers/internalrequests/JakartaRequestInfoServlet.java b/src/test/java/org/apache/sling/servlethelpers/internalrequests/JakartaRequestInfoServlet.java
new file mode 100644
index 0000000..b933eec
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlethelpers/internalrequests/JakartaRequestInfoServlet.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.servlethelpers.internalrequests;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import jakarta.servlet.ServletException;
+import org.apache.commons.io.IOUtils;
+import org.apache.sling.api.SlingJakartaHttpServletRequest;
+import org.apache.sling.api.SlingJakartaHttpServletResponse;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.servlets.SlingJakartaAllMethodsServlet;
+
+class JakartaRequestInfoServlet extends SlingJakartaAllMethodsServlet {
+    private static final long serialVersionUID = 1L;
+    private static final String PREFIX = "TEST_";
+    private final String resolutionInfo;
+
+    JakartaRequestInfoServlet(SlingJakartaHttpServletRequest resolutionRequest) {
+        final StringBuilder sb = new StringBuilder();
+
+        // Verify that we get the usual Sling request
+        // attributes in the resolution call
+        final RequestPathInfo rpi = resolutionRequest.getRequestPathInfo();
+        sb.append("M_").append(resolutionRequest.getMethod());
+        sb.append(" PI_").append(resolutionRequest.getPathInfo());
+        sb.append(" RPI_EXT_").append(rpi.getExtension());
+        sb.append(" RPI_SEL_").append(rpi.getSelectorString());
+        sb.append(" RPI_P_").append(rpi.getResourcePath());
+        if (resolutionRequest.getResource() != null) {
+            sb.append(" RT_").append(resolutionRequest.getResource().getResourceType());
+            sb.append(" RST_").append(resolutionRequest.getResource().getResourceSuperType());
+        }
+        if (resolutionRequest.getResource().getResourceResolver() != null) {
+            sb.append(" RRA_")
+                    .append(resolutionRequest
+                            .getResource()
+                            .getResourceResolver()
+                            .getAttribute("testAttribute"));
+        }
+
+        resolutionInfo = sb.toString();
+    }
+
+    @Override
+    public void service(SlingJakartaHttpServletRequest request, SlingJakartaHttpServletResponse response)
+            throws IOException, ServletException {
+        if ("/EXCEPTION".equals(request.getResource().getPath())) {
+            throw new IOException("Failing as designed");
+        }
+        if ("/SERVLET-EXCEPTION".equals(request.getResource().getPath())) {
+            throw new ServletException("Failing as designed");
+        }
+        if ("STATUS".equals(request.getMethod())) {
+            response.setContentType("farenheit");
+            response.sendError(451);
+            return;
+        }
+
+        final SortedMap<String, String> sorted = new TreeMap<>();
+        request.getParameterMap().entrySet().stream().forEach(e -> {
+            sorted.put(e.getKey(), Arrays.asList(e.getValue()).toString());
+        });
+
+        response.getWriter().write(resolutionInfo);
+        response.getWriter().write(" CT_" + request.getContentType());
+        response.getWriter().write(" P_" + sorted);
+        final BufferedReader body = request.getReader();
+        if (body != null) {
+            response.getWriter().write(" B_");
+            IOUtils.copy(body, response.getWriter());
+        }
+        response.getWriter().flush();
+
+        response.setContentType(PREFIX + request.getContentType());
+        response.setContentType("CT_" + request.getContentType());
+    }
+}
diff --git a/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockServletResolver.java b/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockServletResolver.java
index 226e7d0..1c2e976 100644
--- a/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockServletResolver.java
+++ b/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockServletResolver.java
@@ -21,9 +21,12 @@ package org.apache.sling.servlethelpers.internalrequests;
 import javax.servlet.Servlet;
 
 import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingJakartaHttpServletRequest;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.servlets.ServletResolver;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 class MockServletResolver implements ServletResolver {
 
@@ -46,4 +49,19 @@ class MockServletResolver implements ServletResolver {
     public Servlet resolveServlet(ResourceResolver resolver, String scriptName) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public jakarta.servlet.@Nullable Servlet resolve(@NotNull SlingJakartaHttpServletRequest request) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public jakarta.servlet.@Nullable Servlet resolve(@NotNull Resource resource, @NotNull String scriptName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public jakarta.servlet.@Nullable Servlet resolve(@NotNull ResourceResolver resolver, @NotNull String scriptName) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockSlingRequestProcessor.java b/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockSlingRequestProcessor.java
index 3c22901..b9e956c 100644
--- a/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockSlingRequestProcessor.java
+++ b/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockSlingRequestProcessor.java
@@ -25,6 +25,8 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 
 import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingJakartaHttpServletRequest;
+import org.apache.sling.api.SlingServletException;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.engine.SlingRequestProcessor;
 
@@ -42,4 +44,23 @@ class MockSlingRequestProcessor implements SlingRequestProcessor {
             new RequestInfoServlet((SlingHttpServletRequest) request).service(request, response);
         }
     }
+
+    @Override
+    public void processRequest(
+            jakarta.servlet.http.HttpServletRequest httpRequest,
+            jakarta.servlet.http.HttpServletResponse response,
+            ResourceResolver resourceResolver)
+            throws IOException {
+        final SlingJakartaHttpServletRequest request = (SlingJakartaHttpServletRequest) httpRequest;
+        if (request.getResource() != null
+                && "/NOSERVLET".equals(request.getResource().getPath())) {
+            response.sendError(404);
+        } else {
+            try {
+                new JakartaRequestInfoServlet((SlingJakartaHttpServletRequest) request).service(request, response);
+            } catch (final jakarta.servlet.ServletException e) {
+                throw new SlingServletException(e);
+            }
+        }
+    }
 }