Skip to content

Improved HTTP server route matching with performant hybrid implementation - #751

Open
GoodforGod wants to merge 8 commits into
masterfrom
feature/http-server-router
Open

Improved HTTP server route matching with performant hybrid implementation#751
GoodforGod wants to merge 8 commits into
masterfrom
feature/http-server-router

Conversation

@GoodforGod

Copy link
Copy Markdown
Contributor

Improved HTTP server route matching with immutable hybrid radix and decision tries

EN

Improved HTTP route lookup performance and scalability by replacing the mutable legacy runtime matcher with an immutable hybrid matcher that combines exact-path lookup, a compressed radix trie, and per-stem segment decision tries. The final implementation preserves route priority and parameter-capture semantics, validates terminal wildcards at startup, and removes synchronization and volatile state access from request processing.


RU

Улучшена производительность и масштабируемость HTTP-маршрутизации за счёт immutable Hybrid matcher, объединяющего exact-path lookup, compressed radix trie и segment decision trie для конфликтующих групп маршрутов. Финальная реализация сохраняет приоритеты маршрутов и семантику параметров, валидирует wildcard при запуске и убирает синхронизацию и volatile-доступ из request hot path.


  • Added HybridPathTemplateMatcher as the production matcher used by HttpServerRouter.
  • Added RadixPathTemplateMatcher as the compressed-radix baseline and fallback strategy for non-conflicting routes.
  • Improved runtime lookup by compiling all registrations once through builders into immutable matcher snapshots.
  • Added segment decision tries for routes sharing the same static stem, eliminating linear candidate scans in collision-heavy groups.
  • Added startup validation restricting * to the final character of a route template.
  • Preserved Original, OptimizedOriginal, and Decision implementations in test fixtures for correctness checks and JMH comparisons.
  • Added benchmark suites covering ordinary routes, shared stems, fanout shapes, Hybrid strategy thresholds, and isolated single-parameter lookup.

Performance

All reported values use JMH AverageTime in ns/op; lower is better.

The broad comparison used:

  • JDK 25.0.2;
  • one fork;
  • three 500 ms warmup iterations;
  • five 500 ms measurement iterations;
  • one benchmark thread.

Due to lower bench params some regressions or lower final results may be for new impl, next proper detailed recheck affirms that.

Scenario Routes Original OptimizedOriginal Radix Decision Hybrid
Exact 16 18.4 9.9 9.2 8.4 6.7
Exact 128 19.7 9.5 9.9 8.2 7.8
Exact 1024 16.8 8.7 7.0 8.7 5.8
Single parameter¹ 16 57.3 82.2 65.2 114.0 68.0
Single parameter¹ 128 93.6 133.5 121.9 156.2 144.5
Single parameter¹ 1024 106.2 76.6 71.6 133.4 73.1
Multiple parameters 16 109.5 136.3 104.1 207.9 94.2
Multiple parameters 128 119.6 135.2 128.4 201.8 111.7
Multiple parameters 1024 124.2 157.8 108.4 243.4 107.2
Terminal wildcard 16 101.9 64.0 62.5 131.5 55.0
Terminal wildcard 128 115.8 58.3 68.1 119.6 66.4
Terminal wildcard 1024 128.5 79.3 78.4 152.4 77.4
Miss 16 91.4 28.4 26.5 54.4 29.4
Miss 128 154.3 22.0 19.0 41.1 20.6
Miss 1024 133.4 28.7 26.4 54.8 24.5

¹ The short-run single-parameter measurements were non-monotonic and had large errors, especially for Hybrid. They were superseded by the dedicated full-profile run below.

Hybrid provides the best exact-path result at every tested route count. It also wins every multiple-parameter case, two of three wildcard cases, and the 1024-route miss case. Radix remains slightly faster for small misses because it avoids Hybrid’s per-stem strategy dispatch.

Stabilized single-parameter comparison

The dedicated benchmark isolates unique-stem routes of the form:

/api/users/0/{id}
/api/users/1/{id}
/api/users/2/{id}

Only Original and Hybrid were measured using the extended two-fork profile.

The single-param bench comparison used:

  • two forks;
  • five 3-second warmup iterations;
  • ten 2-second measurement iterations;
  • 20 measurement samples per point.
Routes Original Hybrid Hybrid improvement
16 54.124 ± 0.348 43.316 ± 4.059 20.0%, 1.25×
128 63.039 ± 0.896 48.661 ± 1.023 22.8%, 1.30×
1024 61.081 ± 0.436 55.004 ± 1.529 9.9%, 1.11×

Taking the stabilized single-parameter measurements into account, Hybrid wins 12 of the 15 general scenario/route-count combinations.

Shared static stems

This scenario places every route in one conflicting stem group:

/shared/{tenant}/resource-0000/{id}
/shared/{tenant}/resource-0001/{id}
/shared/{tenant}/resource-0002/{id}
Scenario Routes Original OptimizedOriginal Radix Decision Hybrid
Hit 16 1,298.7 878.8 592.5 164.1 116.6
Hit 128 9,128.0 6,568.1 4,629.4 167.3 97.7
Hit 1024 74,906.5 60,352.6 42,791.2 188.1 118.0
Miss 16 1,094.6 771.8 474.9 92.8 74.6
Miss 128 7,607.7 5,736.0 4,353.2 96.0 67.7
Miss 1024 75,484.3 51,221.3 28,755.6 99.8 68.3

At 1024 conflicting routes, Hybrid is faster than:

Comparison Hit Miss
Original / Hybrid 634.9× 1,104.5×
OptimizedOriginal / Hybrid 511.6× 749.5×
Radix / Hybrid 362.7× 420.8×
Decision / Hybrid 1.59× 1.46×

Fanout and decision-threshold comparison

The fanout benchmark keeps 128 total routes and varies candidates per shared stem from 1 to 128.

It covers:

  • EARLY_FANOUT, where literal alternatives appear immediately after the shared prefix;
  • DEEP_FANOUT, where alternatives appear after additional common parameter and literal segments;
  • WILDCARD_FANOUT, where each candidate ends in a terminal wildcard;
  • successful and unsuccessful lookups;
  • Original, OptimizedOriginal, Radix, Decision, and default Hybrid;
  • forced always-linear and always-decision Hybrid policies.

Geometric means across all group sizes:

Scenario Original OptimizedOriginal Radix Decision Hybrid
Early fanout hit 920.2 780.4 535.7 175.0 112.2
Early fanout miss 899.7 666.0 386.8 115.5 73.2
Deep fanout hit 1,656.8 1,407.4 1,361.3 338.7 268.1
Deep fanout miss 1,546.2 1,283.2 1,177.6 171.4 137.2
Wildcard fanout hit 1,061.1 740.6 587.1 181.8 139.0
Wildcard fanout miss 866.8 626.3 371.4 115.3 73.2

Hybrid wins 42 of the 48 exact shape × groupSize × hit/miss combinations.

Diagnostic Hybrid modes confirm that decision compilation provides the improvement:

Scenario Default Hybrid Always decision Always linear
Early fanout hit 112.2 112.5 530.8
Early fanout miss 73.2 71.1 390.4
Deep fanout hit 268.1 262.4 1,363.7
Deep fanout miss 137.2 134.7 1,210.5
Wildcard fanout hit 139.0 135.5 595.8
Wildcard fanout miss 73.2 73.3 394.1

DEFAULT_DECISION_THRESHOLD = 2 therefore keeps the direct single-holder path when a stem uniquely identifies one route and compiles every genuine collision into a decision trie. The default policy tracks always-decision performance, while forced linear matching regresses toward Radix as collision groups grow.


Design

Exact-path index

Routes without parameters or wildcards are compiled into an immutable exact-path map.

Lookup begins with:

exactPaths.get(path)

The stored result already contains:

  • matched template;
  • empty parameter map;
  • handler value.

Exact hits therefore require no trie traversal and reuse a prebuilt match result.

This is why Hybrid produces the best exact-path result at all tested route counts.

Compressed radix stem index

Dynamic routes are grouped by their static stem and inserted into a compressed radix tree.

A regular character trie would represent:

/api/users/

Per-stem strategy selection

Each radix terminal is compiled into one of three forms:

  1. Single holder
    Used when the stem uniquely identifies one route. No candidate list or decision trie is needed.

  2. Linear matcher
    Retained for groups that cannot safely start segment decisions at the stem boundary.

  3. Decision matcher
    Used for compatible stem groups with at least DEFAULT_DECISION_THRESHOLD candidates.

The default threshold is two.

Wildcard contract

A wildcard is valid only when * is the final character of the template.

Valid examples:

/*
/files/*
/users/{id}/*
/projects/{projectId}/assets/*

Invalid examples:

/files/*/metadata
/files/**/
/files/*/{id}

Invalid templates fail during router construction rather than producing ambiguous or implementation-dependent runtime matching.

Terminal wildcards participate in normal route priority and may follow literal or parameter segments.

Compatibility

The public HttpServerRouter construction and routing contract remains unchanged.

The matcher preserves:

  • exact route priority;
  • literal-over-parameter ordering;
  • parameter capture names and values;
  • terminal wildcard capture;
  • trailing-slash configuration;
  • equivalent-template rejection;
  • 404 and 405 behavior;
  • interceptor-chain execution after route resolution.

The stricter wildcard validation intentionally rejects templates containing a non-terminal * during startup.


Validation

  • Passed 365 HTTP server common tests with zero failures and zero errors.
  • Compiled production, test, test-fixture, and JMH source sets.
  • Verified wildcard behavior across legacy and production matchers.
  • Verified that builder mutations do not affect already-built matcher snapshots.
  • Verified that Hybrid and Radix runtime lookup states are final and require no volatile or synchronized access.
  • Validated all benchmark JSON artifacts:
    • 75 general result rows;
    • 30 shared-stem result rows;
    • 336 fanout and strategy result rows;
    • 6 full-profile single-parameter result rows.

Renamed HttpServerHandler to HttpServerRouter
Optimized and improved HttpServerRouter
Optimized and improved PathTemplateMatcher & PathTemplate
…erminal wildcard validation

Improved route lookup performance by introducing compressed radix and adaptive hybrid decision matching with builder-compiled immutable state.

- Added strict validation that wildcard segments are allowed only at the end of a route.
- Moved legacy matcher variants to test fixtures for compatibility benchmarks.
- Added benchmarks covering shared stems, route groups, and matcher strategies.
@GoodforGod GoodforGod added this to the v2.0.0 milestone Jul 27, 2026
@GoodforGod
GoodforGod requested a review from Squiry July 27, 2026 08:46
@GoodforGod GoodforGod added module: http Related to HTTP module optimization Optimization change that doesn't change behavior or bring something new labels Jul 27, 2026
@GoodforGod GoodforGod added the breaking change API breaking change label Jul 27, 2026
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

Test Results

158 tests   158 ✅  11m 22s ⏱️
  5 suites    0 💤
  5 files      0 ❌

Results for commit 101d785.

♻️ This comment has been updated with latest results.

@GoodforGod

GoodforGod commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Change for Wildcard

Now a wildcard is valid only when * is the final character of the template.

Valid examples:

/*
/files/*
/users/{id}/*
/projects/{projectId}/assets/*

Invalid examples:

/files/*/metadata
/files/**/
/files/*/{id}

Invalid templates fail during router construction rather than producing ambiguous or implementation-dependent runtime matching.

Terminal wildcards participate in normal route priority and may follow literal or parameter segments.

@GoodforGod GoodforGod changed the title Improved HTTP server route matching with immutable hybrid radix and decision tries Improved HTTP server route matching performant implementation Jul 27, 2026
@GoodforGod GoodforGod changed the title Improved HTTP server route matching performant implementation Improved HTTP server route matching with performant hybrid implementation Jul 27, 2026
@github-actions

Copy link
Copy Markdown

Dependency Update Report

Update level: patch

Found 3 dependency updates.

gradle/libs.versions.toml

  • s3client-aws (software.amazon.awssdk:s3, inline:248): 2.47.5 -> 2.47.6
  • zeebe: 8.9.12 -> 8.9.13
  • kotlin-stdlib: 2.4.0 -> 2.4.10

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change API breaking change module: http Related to HTTP module optimization Optimization change that doesn't change behavior or bring something new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant