Skip to main content

Path Directives

Path directives match and consume segments of the request URI path. They work in conjunction with path matchers to extract typed values from path segments.

Path Matching Directives​

path​

Matches the remaining path completely (after consuming a leading slash) against the given path matcher. The path must be fully consumed for the directive to pass.

val route: Route = path("hello") {
complete("Hello!")
}
// Matches: /hello
// Does not match: /hello/world

With extraction:

val route: Route = path("user" / IntNumber) { userId =>
complete(s"User $userId")
}
// Matches: /user/42 (extracts 42)

pathPrefix​

Matches a prefix of the remaining path after consuming a leading slash. The rest of the path is left for inner directives to match.

val route: Route = pathPrefix("api" / "v1") {
path("users") {
complete("Users endpoint")
}
}
// Matches: /api/v1/users

rawPathPrefix​

Like pathPrefix, but does not implicitly consume a leading slash. Applies the matcher directly to the unmatched path.

val route: Route = rawPathPrefix(Slash ~ "api") {
complete("API")
}

pathPrefixTest​

Like pathPrefix, but does not consume the matched path prefix. Useful for lookahead checks.

val route: Route = pathPrefixTest("api") {
// The "api" segment is still available for inner directives
pathPrefix("api" / "v1") {
complete("v1")
}
}

rawPathPrefixTest​

Like rawPathPrefix, but does not consume the matched path prefix. Tests whether the unmatched path has the given prefix using raw (unencoded) matching, without implicitly consuming a leading slash.

val route: Route = rawPathPrefixTest(Slash ~ "api") {
// The matched prefix is still available for inner directives
rawPathPrefix(Slash ~ "api") {
complete("API")
}
}

pathSuffix​

Matches a suffix of the remaining unmatched path and consumes it. The matcher operates in reversed-segment order.

val route: Route = pathSuffix("json") {
complete("JSON endpoint")
}
// pathSuffix("baz" / "bar") matches /foo/bar/baz

pathSuffixTest​

Like pathSuffix, but does not consume the matched suffix.

val route: Route = pathSuffixTest("json") {
complete("JSON detected")
}

pathEnd​

Matches only if the remaining unmatched path is empty. Use this to ensure the path has been fully consumed.

val route: Route = pathPrefix("api") {
pathEnd {
complete("API root")
}
}
// Matches: /api
// Does not match: /api/users

pathEndOrSingleSlash​

Matches if the remaining unmatched path is empty or consists of a single trailing slash.

val route: Route = pathPrefix("api") {
pathEndOrSingleSlash {
complete("API root")
}
}
// Matches: /api and /api/

pathSingleSlash​

Matches only if the remaining path is a single slash.

val route: Route = pathSingleSlash {
complete("Root with slash")
}
// Matches: /

ignoreTrailingSlash​

Tries the inner route. If it rejects with empty rejections, retries with the trailing slash toggled (added or removed).

val route: Route = ignoreTrailingSlash {
path("hello") {
complete("Hello!")
}
}
// Matches both: /hello and /hello/

redirectToTrailingSlashIfMissing​

If the request path does not end with a slash, redirects to the same URI with a trailing slash appended.

val route: Route = redirectToTrailingSlashIfMissing(Status.MovedPermanently) {
path("dir" /) {
complete("Directory listing")
}
}
// GET /dir -> 301 redirect to /dir/

redirectToNoTrailingSlashIfPresent​

If the request path ends with a slash (and is not the root path /), redirects to the same URI without the trailing slash.

val route: Route = redirectToNoTrailingSlashIfPresent(Status.Found) {
pathPrefix("users") {
concat(
pathEnd {
complete("User list")
},
path(Segment) { userId =>
complete(s"User $userId")
}
)
}
}
// GET /users/ -> 302 redirect to /users

Path Matchers​

Path matchers are applied to the unmatched portion of the request path. They consume path segments, extract typed values, and can be composed with operators.

Slash​

Matches a single slash character (/).

PathEnd​

Matches the very end of the path (empty remaining path with no trailing slash).

Remaining​

Matches and extracts the entire remaining unmatched path as a String.

val route: Route = path("files" / Remaining) { filePath =>
complete(s"File: $filePath")
}
// /files/a/b/c -> extracts "a/b/c"

RemainingPath​

Matches and extracts the entire remaining unmatched path as a Uri.Path value.

val route: Route = path("proxy" / RemainingPath) { remainingPath =>
complete(s"Forwarding: $remainingPath")
}

Segment​

Matches a single path segment and extracts it as a String.

val route: Route = path("user" / Segment) { name =>
complete(s"User: $name")
}
// /user/alice -> extracts "alice"

Segments​

Matches up to 128 remaining segments and extracts them as a List[String]. Can also be parameterized with a count or range.

val route: Route = path("browse" / Segments) { segments =>
complete(s"Path: ${segments.mkString("/")}")
}
// Exactly 3 segments
path("a" / Segments(3)) { segments => complete(segments.toString) }

// Between 1 and 5 segments
path("b" / Segments(1, 5)) { segments => complete(segments.toString) }

IntNumber​

Matches a sequence of decimal digits and extracts their Int value. Does not match values exceeding Int.MaxValue.

val route: Route = path("item" / IntNumber) { id =>
complete(s"Item $id")
}

LongNumber​

Matches a sequence of decimal digits and extracts their Long value. Does not match values exceeding Long.MaxValue.

val route: Route = path("record" / LongNumber) { id =>
complete(s"Record $id")
}

HexIntNumber​

Matches a sequence of hexadecimal digits and extracts their Int value.

val route: Route = path("color" / HexIntNumber) { color =>
complete(s"Color: 0x${color.toHexString}")
}

HexLongNumber​

Matches a sequence of hexadecimal digits and extracts their Long value.

DoubleNumber​

Matches a decimal number (optionally signed, with optional decimal point) and extracts its Double value.

val route: Route = path("price" / DoubleNumber) { price =>
complete(s"Price: $price")
}

JavaUUID​

Matches a UUID string in standard format and extracts a java.util.UUID.

val route: Route = path("entity" / JavaUUID) { uuid =>
complete(s"Entity: $uuid")
}
// /entity/550e8400-e29b-41d4-a716-446655440000

Neutral​

Always matches, consumes nothing, and extracts nothing. Serves as the neutral element in path matcher composition.

Matcher Composition​

/ (Slash Concatenation)​

Joins two matchers with an implicit slash between them.

val matcher = "api" / "v1" / IntNumber
// Matches: api/v1/42

val route: Route = path(matcher) { id =>
complete(s"ID: $id")
}

~ (Direct Append)​

Concatenates two matchers directly without inserting a slash.

val matcher = "item-" ~ IntNumber
// Matches the segment: item-42

val route: Route = path(matcher) { id =>
complete(s"Item $id")
}

| (Alternatives)​

Tries the left matcher first; if it does not match, tries the right matcher. Both matchers must produce the same extraction type.

val matcher = "users" | "people"
// Matches: users or people

val route: Route = path(matcher) {
complete("Person list")
}

? / optional​

Makes a matcher optional. If the matcher does not match, the directive still passes but extracts None (for PathMatcher1) or Unit (for PathMatcher0).

val route: Route = path("items" / IntNumber.?) { maybeId =>
complete(s"ID: $maybeId")
}
// /items/42 -> Some(42)
// /items -> None

repeat(count) and repeat(min, max, separator)​

Matches the underlying matcher a specified number of times, extracting a List of results.

// Exactly 3 segments
val route: Route = path(Segment.repeat(3, separator = Slash)) { segments =>
complete(segments.mkString(", "))
}
// Between 1 and 5 integer segments
val route: Route = path(IntNumber.repeat(1, 5, separator = Slash)) { numbers =>
complete(numbers.mkString(", "))
}

tmap and tflatMap​

Transform the extracted tuple value of a matcher.

val evenNumber = IntNumber.tmap { case Tuple1(n) => Tuple1(n * 2) }

val route: Route = path("double" / evenNumber) { n =>
complete(s"Doubled: $n")
}
val positiveNumber = IntNumber.tflatMap {
case Tuple1(n) if n > 0 => Some(Tuple1(n))
case _ => None
}

val route: Route = path("positive" / positiveNumber) { n =>
complete(s"Positive: $n")
}