"Is it actually a good idea to run outside right now?" has at least four inputs:
- Weather — but the thermometer lies; 60°F at 90% humidity feels like 80°F.
- Air quality — a runner breathing hard inhales far more of it than the sedentary person the AQI bands were written for.
- Ground-level ozone — peaks with afternoon heat and sunlight, exactly when people go out. It is the reason a clear, warm, apparently perfect afternoon burns the back of your throat.
- Severe weather alerts — 72°F and clear is still no time to run under a tornado warning.
Those live in four different apps, and nobody checks all four. RunWild fetches them concurrently, scores every hour of the next two days against your tolerances (heat ceiling, pollen sensitivity, run length), and names the single best window — and exactly why every other hour lost points.
No API keys. No account. No cloud. $0 to run. One mvn package, one command, on the laptop you already own — and the dashboard is reachable from your phone on the same network.
Run the CLI and you get a terminal report: the best window, ranked alternatives, and an hour-by-hour score bar for the next 24 hours. Run --serve and the same engine drives a browser dashboard on the JDK's own HTTP server — verdict, ranked windows, an hour-by-hour table, a per-hour score breakdown, and live controls so you can change your pollen sensitivity or heat ceiling and watch the recommendation move.
On a genuinely unpleasant August evening in Grand Junction, Colorado — feels-like 94°F, an active Heat Advisory — RunWild rejected the whole afternoon (23/100 at 3 PM), rated the small hours as merely good because of a darkness safety penalty, and landed on 7:00 AM: the one hour that is both light and cool. That is a different answer from "what is the warmest hour today."
Java 26 is the point, not a checkboxEvery modern-Java feature here is used because it was the right tool:
- HTTP/3 in HttpClient — honestly. Setting
.version(HTTP_3)alone is not enough: default ALT_SVC discovery silently downgrades the first request, so RunWild setsHttpOption.H3_DISCOVERYexplicitly and reports the protocol actually negotiated, per source, in the UI. A--h3-demomode usesHTTP_3_URI_ONLY— which fails outright rather than downgrading — to prove h3 is genuinely negotiated over QUIC. - Structured concurrency (StructuredTaskScope, JEP 505 preview). Three independent sources fetched at once: 2.3× faster, measured by the built-in
--benchmarkmode (627 ms concurrent vs 1440 ms sequential). The scope guarantees no subtask outlives it — if weather fails, the air-quality fetch is cancelled, not leaked. - Stream Gatherers (windowSliding). You do not run for an instant, you run for an hour. Finding the best contiguous block is literally a sliding window, so it is one stream operation instead of a hand-rolled index loop.
- ScopedValue. Binds each request's tolerances for the dynamic extent of one request, so two people can hit the same server and get answers tuned to their own hay fever — with nothing to leak or clean up, unlike a ThreadLocal.
- Virtual threads.
newVirtualThreadPerTaskExecutor()on the JDK's built-in HTTP server. One thread per request, no pool tuning, no framework — the whole web tier is the JDK. - Sealed interfaces + records + exhaustive switch with record patterns. Every reason an hour loses points is a record in a sealed Advisory hierarchy. Consumers switch with no default branch, so adding a new advisory is a compile error until every consumer explains it. Unnamed patterns (
case Heat _ ->) match on shape when the payload is irrelevant.
One feature I measured and rejected: the AOT cache (Project Leyden). Over five runs each, median startup was 700 ms without the cache and 1163 ms with a 12.7 MB cache — loading it costs more than it saves for an app dominated by network round trips. It is not used. Reporting that felt more useful than an unverified feature list.
How the score worksEvery hour starts at 100 and loses points: apparent temperature outside your ideal band, US AQI bands, ground-level ozone (EPA 8-hour standard), pollen multiplied by your sensitivity, UV, rain probability, wind, and a flat darkness safety penalty. A severe weather alert caps the score at 20 no matter what else is true. Nothing is hidden — the dashboard shows the itemised deduction for any hour you select.
A window is scored as 70% its average and 30% its worst hour. Pure averaging happily recommends a block containing one genuinely miserable hour as long as its neighbours are pleasant; weighting the worst hour prefers a flat, reliably decent window over a spiky one.
Missing data is never scored as good data. Open-Meteo's pollen model covers Europe only — US coordinates return every hourly value as null. RunWild says so plainly and scores ground-level ozone instead, which has full US coverage and is arguably the more relevant respiratory factor for a runner anyway. Scoring an unknown AQI as clean air would be confidently wrong.
Built like it matters26 tests, zero network. The fixtures are real captured API responses, not hand-written samples, so the suite catches upstream shape changes. Coverage includes every scoring band; that a severe alert caps an otherwise perfect hour and an expired or Moderate one does not; that pollen sensitivity 0 and 2 produce different answers from identical air; that missing data yields an advisory rather than a penalty; and that the window ranking rejects a spiky block in favour of a flat one.
Accessibility designed in, not retrofitted. Colour never carries meaning alone — every score is a number and a word, so the page works in greyscale. The hourly view is a real table with a caption and row headers, rows are keyboard-operable, the verdict is an aria-live region, and the page honours prefers-reduced-motion, prefers-contrast, and light/dark via color-scheme and light-dark().
Limitations, honestly- NWS alerts are US-only. Outside the US that source returns nothing and RunWild says so; the rest still works.
- Pollen is Europe-only upstream, as described above.
- HTTP/3 depends on the host and network. Open-Meteo does not currently advertise it, and some proxies and VPNs block QUIC. RunWild attempts h3 on every source, falls back cleanly, and always reports the protocol it really got — a dashboard that printed HTTP/3 while running over HTTP/1.1 would be lying.
- Scoring thresholds follow EPA breakpoints where those exist, but this is decision support, not medical advice.
- A laptop or desktop you already own — $0
- JDK 26 (Adoptium Temurin) — free
- API keys, accounts, cloud services — none
Requires JDK 26. Clone the repo, then:
mvn clean package
java --enable-preview -jar target/runwild.jar --plan # terminal report
java --enable-preview -jar target/runwild.jar --serve # dashboard on :8080
java --enable-preview -jar target/runwild.jar --h3-demo # prove HTTP/3 is real
java --enable-preview -jar target/runwild.jar --benchmark--enable-preview is needed because StructuredTaskScope is still a preview API in Java 26; everything else used here — HTTP/3, Gatherers, ScopedValue, virtual threads, record patterns — is final.
Full source, tests, and captured sample output: github.com/darcy0408/runwild-java26
RunWild answers one question before you leave the house: given the next 48 hours, when should I go? It reads weather, air quality, ground-level ozone and severe-alert feeds, scores every hour against your own tolerances — your heat ceiling, your pollen sensitivity, how long you plan to be out — and names the single best window, with the itemised deductions that explain what cost every other hour its points.
That is decision support rather than a dashboard. It does not hand you four data feeds and wish you luck; it weighs them against you personally, makes a recommendation you can act on, and then shows its work so you are free to disagree with it. The running is what happens afterwards — the part that lives in your home is the daily judgement call about whether to go at all, and that is the part this automates.










Comments