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")
}