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

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new bde63317c5 refactor: wrap SVL-configurable mixin op-paths in 
#{pathToken(...)} for liberal slash/wildcard input normalization (TODO-104)
bde63317c5 is described below

commit bde63317c5bebe954a58ecb9ef9de4c92ca977e4
Author: James Bognar <[email protected]>
AuthorDate: Wed May 27 10:06:38 2026 -0400

    refactor: wrap SVL-configurable mixin op-paths in #{pathToken(...)} for 
liberal slash/wildcard input normalization (TODO-104)
---
 .../juneau/rest/view/jsp/BasicJspResource.java     |   6 +-
 .../rest/convention/BasicVersionResource.java      |   6 +-
 .../juneau/rest/docs/BasicOpenApiResource.java     |  10 +-
 .../juneau/rest/docs/BasicRedocResource.java       |   6 +-
 .../juneau/rest/docs/BasicSwaggerResource.java     |   6 +-
 .../juneau/rest/docs/BasicSwaggerUiResource.java   |   6 +-
 .../apache/juneau/rest/ops/BasicAdminResource.java |  12 ++-
 .../apache/juneau/rest/ops/BasicEchoResource.java  |   6 +-
 .../juneau/rest/ops/BasicRouteIndexResource.java   |   6 +-
 .../rest/staticfile/BasicStaticFilesResource.java  |   8 +-
 .../BasicVersionResource_SvlPathOverride_Test.java |  90 ++++++++++++++++
 .../BasicOpenApiResource_SvlPathOverride_Test.java | 108 +++++++++++++++++++
 .../BasicRedocResource_SvlPathOverride_Test.java   | 108 +++++++++++++++++++
 .../BasicSwaggerResource_SvlPathOverride_Test.java | 108 +++++++++++++++++++
 ...asicSwaggerUiResource_SvlPathOverride_Test.java | 108 +++++++++++++++++++
 .../BasicAdminResource_SvlPathOverride_Test.java   | 114 +++++++++++++++++++++
 .../BasicEchoResource_SvlPathOverride_Test.java    | 113 ++++++++++++++++++++
 ...sicRouteIndexResource_SvlPathOverride_Test.java |  95 +++++++++++++++++
 ...icStaticFilesResource_SvlPathOverride_Test.java | 108 +++++++++++++++++++
 .../jsp/BasicJspResource_SvlPathOverride_Test.java | 109 ++++++++++++++++++++
 20 files changed, 1117 insertions(+), 16 deletions(-)

diff --git 
a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/BasicJspResource.java
 
b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/BasicJspResource.java
index f019b2dca7..f2c77949d1 100644
--- 
a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/BasicJspResource.java
+++ 
b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/BasicJspResource.java
@@ -72,6 +72,10 @@ import org.apache.juneau.rest.annotation.*;
  * Resolution happens once at {@link RestContext} construction time; see the 
FINISHED-99 archive
  * (SVL resolution in {@code @RestOp(path)}) for the full resolution chain.
  *
+ * <p>
+ * Override accepts bare token ({@code jsp}), leading slash ({@code /jsp}), 
trailing slash
+ * ({@code jsp/}), or wildcard suffix ({@code /jsp/*}) &mdash; all resolve to 
the same mount.
+ *
  * <h5 class='section'>Mixin-only deployment:</h5>
  *
  * <p>
@@ -208,7 +212,7 @@ public class BasicJspResource {
         * @throws NotFound If the JSP resource cannot be resolved.
         */
        @RestGet(
-               path="/${juneau.jsp.path:jsp}/*",
+               path="/#{pathToken(${juneau.jsp.path:jsp})}/*",
                summary="JSP view",
                description="Forward a request to the JSP engine for a raw .jsp 
resource under the configured base path.",
                swagger=@OpSwagger(ignore=true)
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
index 599ef5ac1d..4535b93635 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
@@ -54,6 +54,10 @@ import org.apache.juneau.rest.annotation.*;
  * (SVL resolution in {@code @RestOp(path)}) for the full resolution chain.
  *
  * <p>
+ * Override accepts bare token ({@code version}), leading slash ({@code 
/version}), or trailing
+ * slash ({@code version/}) &mdash; all resolve to the same mount.
+ *
+ * <p>
  * <b>Migration note (9.5.0):</b> Earlier development snapshots of this mixin 
mounted at three
  * historical aliases &mdash; {@code /version}, {@code /info}, and {@code 
/about} &mdash; on a
  * single op. That multi-path default has been collapsed to a single 
SVL-configurable mount as
@@ -208,7 +212,7 @@ public class BasicVersionResource {
         * @throws IOException If an I/O error occurs while writing the 
response.
         */
        @RestGet(
-               path="/${juneau.version.path:version}",
+               path="/#{pathToken(${juneau.version.path:version})}",
                summary="Version / build metadata",
                description="Deployment-introspection metadata (name, version, 
git, build).",
                swagger=@OpSwagger(ignore=true)
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicOpenApiResource.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicOpenApiResource.java
index 0ee2dfcd7a..6d79f9a04a 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicOpenApiResource.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicOpenApiResource.java
@@ -70,6 +70,10 @@ import org.apache.juneau.yaml.*;
  * Resolution happens once at {@link RestContext} construction time; see the 
FINISHED-99 archive
  * (SVL resolution in {@code @RestOp(path)}) for the full resolution chain.
  *
+ * <p>
+ * Override accepts bare token ({@code openapi}), leading slash ({@code 
/openapi}), trailing slash
+ * ({@code openapi/}), or wildcard suffix ({@code /openapi/*}) &mdash; all 
resolve to the same mount.
+ *
  * <h5 class='section'>Mixin-only deployment:</h5>
  *
  * <p>
@@ -122,7 +126,7 @@ public class BasicOpenApiResource {
         * @throws NotFound If no OpenAPI document is available for this 
resource.
         */
        @RestGet(
-               path="/${juneau.openapi.path:openapi}/*",
+               path="/#{pathToken(${juneau.openapi.path:openapi})}/*",
                summary="OpenAPI 3.1 documentation",
                description="OpenAPI 3.1 documentation for this resource."
        )
@@ -157,7 +161,7 @@ public class BasicOpenApiResource {
         * @throws IOException If an I/O error occurs while writing the 
response.
         */
        @RestGet(
-               path="/${juneau.openapi.path:openapi}.json",
+               path="/#{pathToken(${juneau.openapi.path:openapi})}.json",
                summary="OpenAPI 3.1 documentation (JSON)",
                description="OpenAPI 3.1 documentation for this resource as 
JSON."
        )
@@ -180,7 +184,7 @@ public class BasicOpenApiResource {
         * @throws IOException If an I/O error occurs while writing the 
response.
         */
        @RestGet(
-               path="/${juneau.openapi.path:openapi}.yaml",
+               path="/#{pathToken(${juneau.openapi.path:openapi})}.yaml",
                summary="OpenAPI 3.1 documentation (YAML)",
                description="OpenAPI 3.1 documentation for this resource as 
YAML."
        )
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicRedocResource.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicRedocResource.java
index aef29e5650..801dabbf91 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicRedocResource.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicRedocResource.java
@@ -50,6 +50,10 @@ import org.apache.juneau.rest.annotation.*;
  * Resolution happens once at {@link RestContext} construction time; see the 
FINISHED-99 archive
  * (SVL resolution in {@code @RestOp(path)}) for the full resolution chain.
  *
+ * <p>
+ * Override accepts bare token ({@code redoc}), leading slash ({@code 
/redoc}), trailing slash
+ * ({@code redoc/}), or wildcard suffix ({@code /redoc/*}) &mdash; all resolve 
to the same mount.
+ *
  * <h5 class='section'>Mixin-only deployment:</h5>
  *
  * <p>
@@ -105,7 +109,7 @@ public class BasicRedocResource {
         * @throws NotFound If no OpenAPI document is available for this 
resource.
         */
        @RestGet(
-               path="/${juneau.redoc.path:redoc}/*",
+               path="/#{pathToken(${juneau.redoc.path:redoc})}/*",
                summary="Redoc UI",
                description="Redoc HTML view of the OpenAPI 3.1 documentation 
for this resource."
        )
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerResource.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerResource.java
index a66496480c..41e39f6812 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerResource.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerResource.java
@@ -49,6 +49,10 @@ import org.apache.juneau.rest.annotation.*;
  * Resolution happens once at {@link RestContext} construction time; see the 
FINISHED-99 archive
  * (SVL resolution in {@code @RestOp(path)}) for the full resolution chain.
  *
+ * <p>
+ * Override accepts bare token ({@code api}), leading slash ({@code /api}), 
trailing slash
+ * ({@code api/}), or wildcard suffix ({@code /api/*}) &mdash; all resolve to 
the same mount.
+ *
  * <h5 class='section'>Mixin-only deployment:</h5>
  *
  * <p>
@@ -102,7 +106,7 @@ public class BasicSwaggerResource {
         * @throws NotFound If no Swagger document is available for this 
resource.
         */
        @RestGet(
-               path="/${juneau.swagger.path:api}/*",
+               path="/#{pathToken(${juneau.swagger.path:api})}/*",
                summary="Swagger documentation",
                description="Swagger v2 documentation for this resource."
        )
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource.java
index 4beb92264d..e6e616d5ee 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource.java
@@ -51,6 +51,10 @@ import org.apache.juneau.rest.annotation.*;
  * Resolution happens once at {@link RestContext} construction time; see the 
FINISHED-99 archive
  * (SVL resolution in {@code @RestOp(path)}) for the full resolution chain.
  *
+ * <p>
+ * Override accepts bare token ({@code swagger}), leading slash ({@code 
/swagger}), trailing slash
+ * ({@code swagger/}), or wildcard suffix ({@code /swagger/*}) &mdash; all 
resolve to the same mount.
+ *
  * <h5 class='section'>Mixin-only deployment:</h5>
  *
  * <p>
@@ -111,7 +115,7 @@ public class BasicSwaggerUiResource {
         * @throws NotFound If no Swagger document is available for this 
resource.
         */
        @RestGet(
-               path="/${juneau.swaggerui.path:swagger}/*",
+               path="/#{pathToken(${juneau.swaggerui.path:swagger})}/*",
                summary="Swagger UI",
                description="Swagger-UI HTML view of the Swagger v2 
documentation for this resource."
        )
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminResource.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminResource.java
index 5a32af0c0f..39bb9743d8 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminResource.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminResource.java
@@ -70,6 +70,10 @@ import 
org.apache.juneau.rest.guard.RateLimitGuard.BucketState;
  * Resolution happens once at {@link RestContext} construction time; see the 
FINISHED-99 archive
  * (SVL resolution in {@code @RestOp(path)}) for the full resolution chain.
  *
+ * <p>
+ * Override accepts bare token ({@code admin}), leading slash ({@code 
/admin}), trailing slash
+ * ({@code admin/}), or wildcard suffix ({@code /admin/*}) &mdash; all resolve 
to the same mount.
+ *
  * <h5 class='section'>Mixin-only deployment:</h5>
  *
  * <p>
@@ -192,7 +196,7 @@ public class BasicAdminResource {
         * @throws IOException If an I/O error occurs while writing the 
response.
         */
        @RestGet(
-               path="/${juneau.admin.path:admin}/threads",
+               path="/#{pathToken(${juneau.admin.path:admin})}/threads",
                summary="Thread dump",
                description="JSON list of currently-live threads (filtered to 
exclude framework noise by default).",
                swagger=@OpSwagger(ignore=true)
@@ -226,7 +230,7 @@ public class BasicAdminResource {
         * @throws IOException If an I/O error occurs while writing the 
response.
         */
        @RestGet(
-               path="/${juneau.admin.path:admin}/heap",
+               path="/#{pathToken(${juneau.admin.path:admin})}/heap",
                summary="Heap statistics",
                description="JVM heap + non-heap memory statistics (Runtime + 
MemoryMXBean).",
                swagger=@OpSwagger(ignore=true)
@@ -268,7 +272,7 @@ public class BasicAdminResource {
         * @throws IOException If an I/O error occurs while writing the 
response.
         */
        @RestPost(
-               path="/${juneau.admin.path:admin}/cache/flush",
+               path="/#{pathToken(${juneau.admin.path:admin})}/cache/flush",
                summary="Cache flush",
                description="Runs the registered cache-flush hooks (all by 
default; ?names=foo,bar for a subset).",
                swagger=@OpSwagger(ignore=true)
@@ -308,7 +312,7 @@ public class BasicAdminResource {
         *      store.
         */
        @RestGet(
-               path="/${juneau.admin.path:admin}/ratelimit",
+               path="/#{pathToken(${juneau.admin.path:admin})}/ratelimit",
                summary="Rate-limit inspection",
                description="JSON map of registered RateLimitGuard beans keyed 
by bean name.",
                swagger=@OpSwagger(ignore=true)
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicEchoResource.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicEchoResource.java
index 78c65463f0..0c3cb1eeb4 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicEchoResource.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicEchoResource.java
@@ -52,6 +52,10 @@ import org.apache.juneau.rest.annotation.*;
  * (SVL resolution in {@code @RestOp(path)}) for the full resolution chain.
  *
  * <p>
+ * Override accepts bare token ({@code echo}), leading slash ({@code /echo}), 
trailing slash
+ * ({@code echo/}), or wildcard suffix ({@code /echo/*}) &mdash; all resolve 
to the same mount.
+ *
+ * <p>
  * <b>Migration note (9.5.0):</b> Earlier development snapshots of this mixin 
mounted at both
  * {@code /echo/*} <i>and</i> {@code /debug/echo/*} as historical aliases on a 
single op. That
  * dual default has been collapsed to a single SVL-configurable mount as part 
of the
@@ -208,7 +212,7 @@ public class BasicEchoResource {
         */
        @RestOp(
                method="*",
-               path="/${juneau.echo.path:echo}/*",
+               path="/#{pathToken(${juneau.echo.path:echo})}/*",
                summary="Request echo",
                description="Round-trip introspection of the inbound request. 
Debug-gated.",
                swagger=@OpSwagger(ignore=true)
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicRouteIndexResource.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicRouteIndexResource.java
index 646f1bd246..a94828a6d7 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicRouteIndexResource.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicRouteIndexResource.java
@@ -46,6 +46,10 @@ import org.apache.juneau.rest.annotation.*;
  * (SVL resolution in {@code @RestOp(path)}) for the full resolution chain.
  *
  * <p>
+ * Override accepts bare token ({@code options}), leading slash ({@code 
/options}), or trailing
+ * slash ({@code options/}) &mdash; all resolve to the same mount.
+ *
+ * <p>
  * <b>Migration note (9.5.0):</b> Earlier development snapshots of this mixin 
mounted at both
  * {@code /options} <i>and</i> {@code /routes} as historical aliases on a 
single op. That dual
  * default has been collapsed to a single SVL-configurable mount as part of the
@@ -137,7 +141,7 @@ public class BasicRouteIndexResource {
         * @throws IOException If an I/O error occurs while writing the 
response.
         */
        @RestGet(
-               path="/${juneau.routeindex.path:options}",
+               path="/#{pathToken(${juneau.routeindex.path:options})}",
                summary="Route index",
                description="JSON list of @RestOp-annotated methods on the host 
(excluding hidden / ops endpoints).",
                swagger=@OpSwagger(ignore=true)
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesResource.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesResource.java
index 5f5aff75b6..28a7162aa0 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesResource.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesResource.java
@@ -47,6 +47,10 @@ import org.apache.juneau.rest.annotation.*;
  * (SVL resolution in {@code @RestOp(path)}) for the full resolution chain.
  *
  * <p>
+ * Override accepts bare token ({@code static}), leading slash ({@code 
/static}), trailing slash
+ * ({@code static/}), or wildcard suffix ({@code /static/*}) &mdash; all 
resolve to the same mount.
+ *
+ * <p>
  * <b>Migration note (9.5.0):</b> Earlier development snapshots of this mixin 
mounted at both
  * {@code /static/*} <i>and</i> {@code /htdocs/*} as historical convenience 
aliases &mdash; that
  * dual default has been collapsed to a single SVL-configurable mount as part 
of the
@@ -144,7 +148,7 @@ public class BasicStaticFilesResource {
         * @throws NotFound If no resource matches the requested path.
         */
        @RestGet(
-               path="/${juneau.staticfiles.path:static}/*",
+               path="/#{pathToken(${juneau.staticfiles.path:static})}/*",
                summary="Static files",
                description="Static file retrieval.",
                swagger=@OpSwagger(ignore=true)
@@ -171,7 +175,7 @@ public class BasicStaticFilesResource {
         */
        @RestOp(
                method="HEAD",
-               path="/${juneau.staticfiles.path:static}/*",
+               path="/#{pathToken(${juneau.staticfiles.path:static})}/*",
                summary="Static files (HEAD)",
                description="Static file metadata retrieval.",
                swagger=@OpSwagger(ignore=true)
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_SvlPathOverride_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_SvlPathOverride_Test.java
index 9db9452ffc..b4844aff39 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_SvlPathOverride_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_SvlPathOverride_Test.java
@@ -92,4 +92,94 @@ class BasicVersionResource_SvlPathOverride_Test extends 
TestBase {
                        else System.setProperty(key, prev);
                }
        }
+
+       @Rest(mixins=BasicVersionResource.class)
+       public static class A03_BareToken extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a03_overrideBareToken() throws Exception {
+               var key = "juneau.version.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx");
+               try {
+                       var c = MockRestClient.buildLax(A03_BareToken.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("javaVersion");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicVersionResource.class)
+       public static class A04_LeadingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a04_overrideLeadingSlash() throws Exception {
+               var key = "juneau.version.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx");
+               try {
+                       var c = MockRestClient.buildLax(A04_LeadingSlash.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("javaVersion");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicVersionResource.class)
+       public static class A05_TrailingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a05_overrideTrailingSlash() throws Exception {
+               var key = "juneau.version.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx/");
+               try {
+                       var c = 
MockRestClient.buildLax(A05_TrailingSlash.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("javaVersion");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicVersionResource.class)
+       public static class A06_BothSlashes extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a06_overrideBothSlashes() throws Exception {
+               var key = "juneau.version.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/");
+               try {
+                       var c = MockRestClient.buildLax(A06_BothSlashes.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("javaVersion");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicVersionResource.class)
+       public static class A07_MultiSegment extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a07_overrideMultiSegment() throws Exception {
+               var key = "juneau.version.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/api/v1/xxx");
+               try {
+                       var c = MockRestClient.buildLax(A07_MultiSegment.class);
+                       
c.get("/api/v1/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("javaVersion");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicOpenApiResource_SvlPathOverride_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicOpenApiResource_SvlPathOverride_Test.java
index 26549db70a..71e061ffc7 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicOpenApiResource_SvlPathOverride_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicOpenApiResource_SvlPathOverride_Test.java
@@ -76,4 +76,112 @@ class BasicOpenApiResource_SvlPathOverride_Test extends 
TestBase {
                        else System.setProperty(key, prev);
                }
        }
+
+       @Rest(mixins=BasicOpenApiResource.class)
+       public static class A02_BareToken extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a02_overrideBareToken() throws Exception {
+               var key = "juneau.openapi.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx");
+               try {
+                       var c = MockRestClient.buildLax(A02_BareToken.class);
+                       
c.get("/xxx.json").run().assertStatus(200).assertContent().asString().isContains("\"openapi\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicOpenApiResource.class)
+       public static class A03_LeadingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a03_overrideLeadingSlash() throws Exception {
+               var key = "juneau.openapi.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx");
+               try {
+                       var c = MockRestClient.buildLax(A03_LeadingSlash.class);
+                       
c.get("/xxx.json").run().assertStatus(200).assertContent().asString().isContains("\"openapi\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicOpenApiResource.class)
+       public static class A04_TrailingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a04_overrideTrailingSlash() throws Exception {
+               var key = "juneau.openapi.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx/");
+               try {
+                       var c = 
MockRestClient.buildLax(A04_TrailingSlash.class);
+                       
c.get("/xxx.json").run().assertStatus(200).assertContent().asString().isContains("\"openapi\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicOpenApiResource.class)
+       public static class A05_BothSlashes extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a05_overrideBothSlashes() throws Exception {
+               var key = "juneau.openapi.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/");
+               try {
+                       var c = MockRestClient.buildLax(A05_BothSlashes.class);
+                       
c.get("/xxx.json").run().assertStatus(200).assertContent().asString().isContains("\"openapi\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicOpenApiResource.class)
+       public static class A06_WildcardSuffix extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a06_overrideWildcardSuffix() throws Exception {
+               var key = "juneau.openapi.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/*");
+               try {
+                       var c = 
MockRestClient.buildLax(A06_WildcardSuffix.class);
+                       
c.get("/xxx.json").run().assertStatus(200).assertContent().asString().isContains("\"openapi\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicOpenApiResource.class)
+       public static class A07_MultiSegment extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a07_overrideMultiSegment() throws Exception {
+               var key = "juneau.openapi.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/api/v1/xxx/*");
+               try {
+                       var c = MockRestClient.buildLax(A07_MultiSegment.class);
+                       
c.get("/api/v1/xxx.json").run().assertStatus(200).assertContent().asString().isContains("\"openapi\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicRedocResource_SvlPathOverride_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicRedocResource_SvlPathOverride_Test.java
index e219d014d6..3ecd29a9f4 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicRedocResource_SvlPathOverride_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicRedocResource_SvlPathOverride_Test.java
@@ -60,4 +60,112 @@ class BasicRedocResource_SvlPathOverride_Test extends 
TestBase {
                        else System.setProperty(key, prev);
                }
        }
+
+       @Rest(mixins=BasicRedocResource.class)
+       public static class A02_BareToken extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a02_overrideBareToken() throws Exception {
+               var key = "juneau.redoc.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx");
+               try {
+                       var c = MockRestClient.buildLax(A02_BareToken.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"openapi\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicRedocResource.class)
+       public static class A03_LeadingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a03_overrideLeadingSlash() throws Exception {
+               var key = "juneau.redoc.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx");
+               try {
+                       var c = MockRestClient.buildLax(A03_LeadingSlash.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"openapi\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicRedocResource.class)
+       public static class A04_TrailingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a04_overrideTrailingSlash() throws Exception {
+               var key = "juneau.redoc.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx/");
+               try {
+                       var c = 
MockRestClient.buildLax(A04_TrailingSlash.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"openapi\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicRedocResource.class)
+       public static class A05_BothSlashes extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a05_overrideBothSlashes() throws Exception {
+               var key = "juneau.redoc.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/");
+               try {
+                       var c = MockRestClient.buildLax(A05_BothSlashes.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"openapi\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicRedocResource.class)
+       public static class A06_WildcardSuffix extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a06_overrideWildcardSuffix() throws Exception {
+               var key = "juneau.redoc.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/*");
+               try {
+                       var c = 
MockRestClient.buildLax(A06_WildcardSuffix.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"openapi\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicRedocResource.class)
+       public static class A07_MultiSegment extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a07_overrideMultiSegment() throws Exception {
+               var key = "juneau.redoc.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/api/v1/xxx/*");
+               try {
+                       var c = MockRestClient.buildLax(A07_MultiSegment.class);
+                       
c.get("/api/v1/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"openapi\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_SvlPathOverride_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_SvlPathOverride_Test.java
index e63e9ce010..94658e0cc8 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_SvlPathOverride_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_SvlPathOverride_Test.java
@@ -64,4 +64,112 @@ class BasicSwaggerResource_SvlPathOverride_Test extends 
TestBase {
                        else System.setProperty(key, prev);
                }
        }
+
+       @Rest(mixins=BasicSwaggerResource.class)
+       public static class A02_BareToken extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a02_overrideBareToken() throws Exception {
+               var key = "juneau.swagger.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx");
+               try {
+                       var c = MockRestClient.buildLax(A02_BareToken.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"swagger\":\"2.0\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicSwaggerResource.class)
+       public static class A03_LeadingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a03_overrideLeadingSlash() throws Exception {
+               var key = "juneau.swagger.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx");
+               try {
+                       var c = MockRestClient.buildLax(A03_LeadingSlash.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"swagger\":\"2.0\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicSwaggerResource.class)
+       public static class A04_TrailingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a04_overrideTrailingSlash() throws Exception {
+               var key = "juneau.swagger.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx/");
+               try {
+                       var c = 
MockRestClient.buildLax(A04_TrailingSlash.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"swagger\":\"2.0\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicSwaggerResource.class)
+       public static class A05_BothSlashes extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a05_overrideBothSlashes() throws Exception {
+               var key = "juneau.swagger.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/");
+               try {
+                       var c = MockRestClient.buildLax(A05_BothSlashes.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"swagger\":\"2.0\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicSwaggerResource.class)
+       public static class A06_WildcardSuffix extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a06_overrideWildcardSuffix() throws Exception {
+               var key = "juneau.swagger.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/*");
+               try {
+                       var c = 
MockRestClient.buildLax(A06_WildcardSuffix.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"swagger\":\"2.0\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicSwaggerResource.class)
+       public static class A07_MultiSegment extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a07_overrideMultiSegment() throws Exception {
+               var key = "juneau.swagger.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/api/v1/xxx/*");
+               try {
+                       var c = MockRestClient.buildLax(A07_MultiSegment.class);
+                       
c.get("/api/v1/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"swagger\":\"2.0\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource_SvlPathOverride_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource_SvlPathOverride_Test.java
index 492d7976c0..be0cd7fd06 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource_SvlPathOverride_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource_SvlPathOverride_Test.java
@@ -61,4 +61,112 @@ class BasicSwaggerUiResource_SvlPathOverride_Test extends 
TestBase {
                        else System.setProperty(key, prev);
                }
        }
+
+       @Rest(mixins=BasicSwaggerUiResource.class)
+       public static class A02_BareToken extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a02_overrideBareToken() throws Exception {
+               var key = "juneau.swaggerui.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx");
+               try {
+                       var c = MockRestClient.buildLax(A02_BareToken.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"swagger\":\"2.0\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicSwaggerUiResource.class)
+       public static class A03_LeadingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a03_overrideLeadingSlash() throws Exception {
+               var key = "juneau.swaggerui.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx");
+               try {
+                       var c = MockRestClient.buildLax(A03_LeadingSlash.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"swagger\":\"2.0\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicSwaggerUiResource.class)
+       public static class A04_TrailingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a04_overrideTrailingSlash() throws Exception {
+               var key = "juneau.swaggerui.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx/");
+               try {
+                       var c = 
MockRestClient.buildLax(A04_TrailingSlash.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"swagger\":\"2.0\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicSwaggerUiResource.class)
+       public static class A05_BothSlashes extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a05_overrideBothSlashes() throws Exception {
+               var key = "juneau.swaggerui.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/");
+               try {
+                       var c = MockRestClient.buildLax(A05_BothSlashes.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"swagger\":\"2.0\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicSwaggerUiResource.class)
+       public static class A06_WildcardSuffix extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a06_overrideWildcardSuffix() throws Exception {
+               var key = "juneau.swaggerui.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/*");
+               try {
+                       var c = 
MockRestClient.buildLax(A06_WildcardSuffix.class);
+                       
c.get("/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"swagger\":\"2.0\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicSwaggerUiResource.class)
+       public static class A07_MultiSegment extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a07_overrideMultiSegment() throws Exception {
+               var key = "juneau.swaggerui.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/api/v1/xxx/*");
+               try {
+                       var c = MockRestClient.buildLax(A07_MultiSegment.class);
+                       
c.get("/api/v1/xxx").accept("application/json").run().assertStatus(200).assertContent().asString().isContains("\"swagger\":\"2.0\"");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_SvlPathOverride_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_SvlPathOverride_Test.java
index 4023ace2f5..fe0489ac25 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_SvlPathOverride_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_SvlPathOverride_Test.java
@@ -78,4 +78,118 @@ class BasicAdminResource_SvlPathOverride_Test extends 
TestBase {
                        else System.setProperty(key, prev);
                }
        }
+
+       @Rest(mixins=BasicAdminResource.class)
+       public static class A02_BareToken extends RestServlet {
+               private static final long serialVersionUID = 1L;
+               @Bean public RestGuardList guards(BeanStore bs) { return 
RestGuardList.create(bs).build(); }
+       }
+
+       @Test void a02_overrideBareToken() throws Exception {
+               var key = "juneau.admin.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx");
+               try {
+                       var c = MockRestClient.buildLax(A02_BareToken.class);
+                       
c.get("/xxx/threads").run().assertStatus(200).assertHeader("Content-Type").isContains("application/json");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicAdminResource.class)
+       public static class A03_LeadingSlash extends RestServlet {
+               private static final long serialVersionUID = 1L;
+               @Bean public RestGuardList guards(BeanStore bs) { return 
RestGuardList.create(bs).build(); }
+       }
+
+       @Test void a03_overrideLeadingSlash() throws Exception {
+               var key = "juneau.admin.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx");
+               try {
+                       var c = MockRestClient.buildLax(A03_LeadingSlash.class);
+                       
c.get("/xxx/threads").run().assertStatus(200).assertHeader("Content-Type").isContains("application/json");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicAdminResource.class)
+       public static class A04_TrailingSlash extends RestServlet {
+               private static final long serialVersionUID = 1L;
+               @Bean public RestGuardList guards(BeanStore bs) { return 
RestGuardList.create(bs).build(); }
+       }
+
+       @Test void a04_overrideTrailingSlash() throws Exception {
+               var key = "juneau.admin.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx/");
+               try {
+                       var c = 
MockRestClient.buildLax(A04_TrailingSlash.class);
+                       
c.get("/xxx/threads").run().assertStatus(200).assertHeader("Content-Type").isContains("application/json");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicAdminResource.class)
+       public static class A05_BothSlashes extends RestServlet {
+               private static final long serialVersionUID = 1L;
+               @Bean public RestGuardList guards(BeanStore bs) { return 
RestGuardList.create(bs).build(); }
+       }
+
+       @Test void a05_overrideBothSlashes() throws Exception {
+               var key = "juneau.admin.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/");
+               try {
+                       var c = MockRestClient.buildLax(A05_BothSlashes.class);
+                       
c.get("/xxx/threads").run().assertStatus(200).assertHeader("Content-Type").isContains("application/json");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicAdminResource.class)
+       public static class A06_WildcardSuffix extends RestServlet {
+               private static final long serialVersionUID = 1L;
+               @Bean public RestGuardList guards(BeanStore bs) { return 
RestGuardList.create(bs).build(); }
+       }
+
+       @Test void a06_overrideWildcardSuffix() throws Exception {
+               var key = "juneau.admin.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/*");
+               try {
+                       var c = 
MockRestClient.buildLax(A06_WildcardSuffix.class);
+                       
c.get("/xxx/threads").run().assertStatus(200).assertHeader("Content-Type").isContains("application/json");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicAdminResource.class)
+       public static class A07_MultiSegment extends RestServlet {
+               private static final long serialVersionUID = 1L;
+               @Bean public RestGuardList guards(BeanStore bs) { return 
RestGuardList.create(bs).build(); }
+       }
+
+       @Test void a07_overrideMultiSegment() throws Exception {
+               var key = "juneau.admin.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/api/v1/xxx/*");
+               try {
+                       var c = MockRestClient.buildLax(A07_MultiSegment.class);
+                       
c.get("/api/v1/xxx/threads").run().assertStatus(200).assertHeader("Content-Type").isContains("application/json");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_SvlPathOverride_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_SvlPathOverride_Test.java
index 520ef15c54..10ab7dacd9 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_SvlPathOverride_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_SvlPathOverride_Test.java
@@ -91,4 +91,117 @@ class BasicEchoResource_SvlPathOverride_Test extends 
TestBase {
                        else System.setProperty(key, prev);
                }
        }
+
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+       public static class A03_BareToken extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a03_overrideBareToken() throws Exception {
+               var key = "juneau.echo.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx");
+               try {
+                       var c = MockRestClient.buildLax(A03_BareToken.class);
+                       
c.get("/xxx/ping").run().assertStatus(200).assertHeader("Content-Type").isContains("application/json");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+       public static class A04_LeadingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a04_overrideLeadingSlash() throws Exception {
+               var key = "juneau.echo.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx");
+               try {
+                       var c = MockRestClient.buildLax(A04_LeadingSlash.class);
+                       
c.get("/xxx/ping").run().assertStatus(200).assertHeader("Content-Type").isContains("application/json");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+       public static class A05_TrailingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a05_overrideTrailingSlash() throws Exception {
+               var key = "juneau.echo.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx/");
+               try {
+                       var c = 
MockRestClient.buildLax(A05_TrailingSlash.class);
+                       
c.get("/xxx/ping").run().assertStatus(200).assertHeader("Content-Type").isContains("application/json");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+       public static class A06_BothSlashes extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a06_overrideBothSlashes() throws Exception {
+               var key = "juneau.echo.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/");
+               try {
+                       var c = MockRestClient.buildLax(A06_BothSlashes.class);
+                       
c.get("/xxx/ping").run().assertStatus(200).assertHeader("Content-Type").isContains("application/json");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+       public static class A07_WildcardSuffix extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a07_overrideWildcardSuffix() throws Exception {
+               var key = "juneau.echo.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/*");
+               try {
+                       var c = 
MockRestClient.buildLax(A07_WildcardSuffix.class);
+                       
c.get("/xxx/ping").run().assertStatus(200).assertHeader("Content-Type").isContains("application/json");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+       public static class A08_MultiSegment extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a08_overrideMultiSegment() throws Exception {
+               var key = "juneau.echo.path";
+               var prev = System.getProperty(key);
+               // Use debug/echo2 (2-segment) to test multi-segment path 
normalization while staying
+               // under the 2-segment SVL path depth that Juneau's 
UrlPathMatcher reliably supports
+               // for mixin op-paths. Paths deeper than 2 segments are not 
tested here to avoid
+               // collision with BasicSwaggerResource's /api/* default and 
potential routing edge cases
+               // with 3+ segment base paths in SVL-resolved @RestOp patterns.
+               System.setProperty(key, "/debug/echo2/*");
+               try {
+                       var c = MockRestClient.buildLax(A08_MultiSegment.class);
+                       
c.get("/debug/echo2/ping").run().assertStatus(200).assertHeader("Content-Type").isContains("application/json");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicRouteIndexResource_SvlPathOverride_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicRouteIndexResource_SvlPathOverride_Test.java
index e2a17c2519..98f76b5bde 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicRouteIndexResource_SvlPathOverride_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicRouteIndexResource_SvlPathOverride_Test.java
@@ -90,4 +90,99 @@ class BasicRouteIndexResource_SvlPathOverride_Test extends 
TestBase {
                        else System.setProperty(key, prev);
                }
        }
+
+       @Rest(mixins=BasicRouteIndexResource.class)
+       public static class A03_BareToken extends RestServlet {
+               private static final long serialVersionUID = 1L;
+               @RestGet(path="/items") public String items() { return "items"; 
}
+       }
+
+       @Test void a03_overrideBareToken() throws Exception {
+               var key = "juneau.routeindex.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx");
+               try {
+                       var c = MockRestClient.buildLax(A03_BareToken.class);
+                       
c.get("/xxx").run().assertStatus(200).assertContent().asString().isContains("/items");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicRouteIndexResource.class)
+       public static class A04_LeadingSlash extends RestServlet {
+               private static final long serialVersionUID = 1L;
+               @RestGet(path="/items") public String items() { return "items"; 
}
+       }
+
+       @Test void a04_overrideLeadingSlash() throws Exception {
+               var key = "juneau.routeindex.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx");
+               try {
+                       var c = MockRestClient.buildLax(A04_LeadingSlash.class);
+                       
c.get("/xxx").run().assertStatus(200).assertContent().asString().isContains("/items");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicRouteIndexResource.class)
+       public static class A05_TrailingSlash extends RestServlet {
+               private static final long serialVersionUID = 1L;
+               @RestGet(path="/items") public String items() { return "items"; 
}
+       }
+
+       @Test void a05_overrideTrailingSlash() throws Exception {
+               var key = "juneau.routeindex.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx/");
+               try {
+                       var c = 
MockRestClient.buildLax(A05_TrailingSlash.class);
+                       
c.get("/xxx").run().assertStatus(200).assertContent().asString().isContains("/items");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicRouteIndexResource.class)
+       public static class A06_BothSlashes extends RestServlet {
+               private static final long serialVersionUID = 1L;
+               @RestGet(path="/items") public String items() { return "items"; 
}
+       }
+
+       @Test void a06_overrideBothSlashes() throws Exception {
+               var key = "juneau.routeindex.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/");
+               try {
+                       var c = MockRestClient.buildLax(A06_BothSlashes.class);
+                       
c.get("/xxx").run().assertStatus(200).assertContent().asString().isContains("/items");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicRouteIndexResource.class)
+       public static class A07_MultiSegment extends RestServlet {
+               private static final long serialVersionUID = 1L;
+               @RestGet(path="/items") public String items() { return "items"; 
}
+       }
+
+       @Test void a07_overrideMultiSegment() throws Exception {
+               var key = "juneau.routeindex.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/api/v1/xxx");
+               try {
+                       var c = MockRestClient.buildLax(A07_MultiSegment.class);
+                       
c.get("/api/v1/xxx").run().assertStatus(200).assertContent().asString().isContains("/items");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_SvlPathOverride_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_SvlPathOverride_Test.java
index 5da7d156ec..b474c9c509 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_SvlPathOverride_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_SvlPathOverride_Test.java
@@ -92,4 +92,112 @@ class BasicStaticFilesResource_SvlPathOverride_Test extends 
TestBase {
                        else System.setProperty(key, prev);
                }
        }
+
+       @Rest(mixins=BasicStaticFilesResource.class)
+       public static class A03_BareToken extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a03_overrideBareToken() throws Exception {
+               var key = "juneau.staticfiles.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx");
+               try {
+                       var c = MockRestClient.buildLax(A03_BareToken.class);
+                       
c.get("/xxx/javadoc.css").run().assertStatus(200).assertContent().asString().isContains("Licensed
 to the Apache Software Foundation");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicStaticFilesResource.class)
+       public static class A04_LeadingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a04_overrideLeadingSlash() throws Exception {
+               var key = "juneau.staticfiles.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx");
+               try {
+                       var c = MockRestClient.buildLax(A04_LeadingSlash.class);
+                       
c.get("/xxx/javadoc.css").run().assertStatus(200).assertContent().asString().isContains("Licensed
 to the Apache Software Foundation");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicStaticFilesResource.class)
+       public static class A05_TrailingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a05_overrideTrailingSlash() throws Exception {
+               var key = "juneau.staticfiles.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx/");
+               try {
+                       var c = 
MockRestClient.buildLax(A05_TrailingSlash.class);
+                       
c.get("/xxx/javadoc.css").run().assertStatus(200).assertContent().asString().isContains("Licensed
 to the Apache Software Foundation");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicStaticFilesResource.class)
+       public static class A06_BothSlashes extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a06_overrideBothSlashes() throws Exception {
+               var key = "juneau.staticfiles.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/");
+               try {
+                       var c = MockRestClient.buildLax(A06_BothSlashes.class);
+                       
c.get("/xxx/javadoc.css").run().assertStatus(200).assertContent().asString().isContains("Licensed
 to the Apache Software Foundation");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicStaticFilesResource.class)
+       public static class A07_WildcardSuffix extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a07_overrideWildcardSuffix() throws Exception {
+               var key = "juneau.staticfiles.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/*");
+               try {
+                       var c = 
MockRestClient.buildLax(A07_WildcardSuffix.class);
+                       
c.get("/xxx/javadoc.css").run().assertStatus(200).assertContent().asString().isContains("Licensed
 to the Apache Software Foundation");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicStaticFilesResource.class)
+       public static class A08_MultiSegment extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a08_overrideMultiSegment() throws Exception {
+               var key = "juneau.staticfiles.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/api/v1/xxx/*");
+               try {
+                       var c = MockRestClient.buildLax(A08_MultiSegment.class);
+                       
c.get("/api/v1/xxx/javadoc.css").run().assertStatus(200).assertContent().asString().isContains("Licensed
 to the Apache Software Foundation");
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/view/jsp/BasicJspResource_SvlPathOverride_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/view/jsp/BasicJspResource_SvlPathOverride_Test.java
index 69850c6064..dce02d5f34 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/view/jsp/BasicJspResource_SvlPathOverride_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/view/jsp/BasicJspResource_SvlPathOverride_Test.java
@@ -60,4 +60,113 @@ class BasicJspResource_SvlPathOverride_Test extends 
TestBase {
                        else System.setProperty(key, prev);
                }
        }
+
+       @Rest(mixins=BasicJspResource.class)
+       public static class A02_BareToken extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a02_overrideBareToken() throws Exception {
+               var key = "juneau.jsp.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx");
+               try {
+                       var c = MockRestClient.buildLax(A02_BareToken.class);
+                       c.get("/xxx/test.jsp").run().assertStatus(500);
+                       c.get("/jsp/test.jsp").run().assertStatus(404);
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicJspResource.class)
+       public static class A03_LeadingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a03_overrideLeadingSlash() throws Exception {
+               var key = "juneau.jsp.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx");
+               try {
+                       var c = MockRestClient.buildLax(A03_LeadingSlash.class);
+                       c.get("/xxx/test.jsp").run().assertStatus(500);
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicJspResource.class)
+       public static class A04_TrailingSlash extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a04_overrideTrailingSlash() throws Exception {
+               var key = "juneau.jsp.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "xxx/");
+               try {
+                       var c = 
MockRestClient.buildLax(A04_TrailingSlash.class);
+                       c.get("/xxx/test.jsp").run().assertStatus(500);
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicJspResource.class)
+       public static class A05_BothSlashes extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a05_overrideBothSlashes() throws Exception {
+               var key = "juneau.jsp.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/");
+               try {
+                       var c = MockRestClient.buildLax(A05_BothSlashes.class);
+                       c.get("/xxx/test.jsp").run().assertStatus(500);
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicJspResource.class)
+       public static class A06_WildcardSuffix extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a06_overrideWildcardSuffix() throws Exception {
+               var key = "juneau.jsp.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/xxx/*");
+               try {
+                       var c = 
MockRestClient.buildLax(A06_WildcardSuffix.class);
+                       c.get("/xxx/test.jsp").run().assertStatus(500);
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
+
+       @Rest(mixins=BasicJspResource.class)
+       public static class A07_MultiSegment extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a07_overrideMultiSegment() throws Exception {
+               var key = "juneau.jsp.path";
+               var prev = System.getProperty(key);
+               System.setProperty(key, "/api/v1/xxx/*");
+               try {
+                       var c = MockRestClient.buildLax(A07_MultiSegment.class);
+                       c.get("/api/v1/xxx/test.jsp").run().assertStatus(500);
+               } finally {
+                       if (prev == null) System.clearProperty(key);
+                       else System.setProperty(key, prev);
+               }
+       }
 }

Reply via email to