Skip to content

feat: add jvm target (desktop / server)#13

Merged
happycodelucky merged 1 commit into
mainfrom
happycodelucky/pedantic-poitras-57e090
Jun 12, 2026
Merged

feat: add jvm target (desktop / server)#13
happycodelucky merged 1 commit into
mainfrom
happycodelucky/pedantic-poitras-57e090

Conversation

@happycodelucky

Copy link
Copy Markdown
Owner

What

Adds a jvm target to the Reachable KMP library, covering desktop apps (Compose Desktop, Swing, JavaFX) and server-side JVM services on JVM 21+. Same Reachability API as every other platform.

The published jar is architecture-neutral bytecode, so the ARM-only rule (CLAUDE.md §1) constrains only the native slices — Linux/Windows/Intel hosts running the jar are fine, which is the whole point of having a JVM target alongside the native ones.

How it works (and the honest caveat)

The JDK has no connectivity-change callback and no OS validation probe — unlike Apple's nw_path_monitor or Android's NET_CAPABILITY_VALIDATED. So JvmReachability polls java.net.NetworkInterface on the base-class scope (default 5 s) and maps each snapshot via a pure, unit-testable mapJvmInterfaces.

That means isReachable is best-effort, documented as such everywhere it surfaces:

  • "A non-loopback interface is up with a routable address" — captive portals are not detected.
  • isDataMetered is always false (no JDK metering signal).
  • Transport is name-inferredTransport.Other when ambiguous (notably macOS en0, which can be Wi-Fi or wired).
  • Host-only container/hypervisor bridges (docker0, veth*, vmnet*, …) are filtered, so a Docker-running laptop reads offline in airplane mode. VPN tunnels (utun*) count as reachable.
  • No HTTP probe by design — a library phoning a hardcoded host every few seconds is a worse default than an honest weak signal. Consumers needing proof of a working path should make a real request and treat its failure as the signal.

Why it's a small change

Build wiring is one linejvm() in the reachable.kmp-library convention plugin slots straight into the existing targets.withType<KotlinJvmTarget> JVM_21 block (originally written for Android's JVM compilation). The expect seam being a single createSharedReachability() function (CLAUDE.md §4) made this a pure addition with zero changes to common logic — the JVM actual is one line, exactly like Apple's.

Files

Implementation (reachable/src/jvmMain/)

  • JvmReachability.kt — poll-loop StateFlowReachability subclass, with an injected interface source for testing.
  • internal/Mapping.jvm.kt + internal/JvmNetworkInterface.kt — pure interface→status mapping (mirrors the existing Mapping.kt split).
  • Reachability.jvm.kt — public Reachability(pollInterval: Duration = 5.seconds) factory.
  • internal/SharedReachabilityHolder.jvm.kt — one-line createSharedReachability() actual.

Tests (reachable/src/jvmTest/) — 14 mapping tests (classification, bridge exclusion, transport priority, metering-always-false) + 4 poll-loop behavior tests (change detection, single-axis flows, close-stops-loop, real-table smoke test).

Docs & meta — new docs/platforms/jvm.md + nav; JVM coverage threaded through README, CLAUDE.md §1/§4, mise.toml (build:jvm task), CI comment, commonMain KDoc, and the index/installation/getting-started/api-design/lifecycle/validated-vs-available pages. Two LESSONS entries (D-004, D-005).

Verification

  • ./gradlew :reachable:check :reachable-testing:check:reachable:jvmTest + :reachable:compileKotlinJvm ran and passed (confirms the target is wired into check); ktlint/detekt clean on the new source sets; iOS-sim, macOS, and Android-host tests still green.
  • ./gradlew :reachable:linkDebugFrameworkIosArm64 — links clean (no regression to the K/N build).
  • python3 docs/check.py — passes; all new cross-links and the #jvm anchor resolve.

Reviewer notes

  • The polling cadence is exposed as a pollInterval constructor arg (default 5 s); Reachability.shared uses the default and starts eagerly like the Apple singleton.
  • Poll-loop tests run against short real intervals on Dispatchers.Default (no virtual-time scheduler reaches that dispatcher) and let Turbine suspend — no Thread.sleep.
  • Pre-existing, out of scope: the README's "currently 2.3.20 with SKIE 0.10.11" conventions line is stale vs. the catalog (2.3.21 / 0.10.12); left untouched here.

🤖 Generated with Claude Code

Adds a `jvm` target to the Reachable KMP library, covering desktop apps
and server-side JVM services on JVM 21+. The published jar is
architecture-neutral bytecode, so the ARM-only rule (CLAUDE.md §1)
constrains only the native slices, not this jar — Linux/Windows/Intel
hosts running it are fine.

Implementation: the JDK has no connectivity-change callback and no OS
validation probe (unlike Apple's nw_path_monitor / Android's
NET_CAPABILITY_VALIDATED), so JvmReachability polls
java.net.NetworkInterface on the base-class scope (default 5s) and maps
each snapshot via a pure, unit-testable mapJvmInterfaces. isReachable is
therefore best-effort ("a non-loopback interface is up with a routable
address"), captive portals are not detected, isDataMetered is always
false, and transport is name-inferred (Transport.Other when ambiguous,
e.g. macOS en0). Host-only container/hypervisor bridges (docker0,
vmnet*, …) are filtered so a Docker-running laptop reads offline in
airplane mode; VPN tunnels (utun*) count. No HTTP probe by design.

Build wiring is one line — jvm() in the convention plugin slots into the
existing KotlinJvmTarget JVM_21 block (originally written for Android),
and the expect seam being a single createSharedReachability() function
made this a pure addition with zero changes to common logic.

Docs: new platforms/jvm.md plus JVM coverage threaded through the README
(badge, platform table, launch sequence), CLAUDE.md §1/§4, mise.toml,
CI comments, commonMain KDoc, and the index/installation/getting-started/
api-design/lifecycle/validated-vs-available pages.

Verified: ./gradlew :reachable:check :reachable-testing:check (jvmTest +
compileKotlinJvm ran and passed; ktlint/detekt clean; iOS-sim, macOS,
Android-host still green), :reachable:linkDebugFrameworkIosArm64 links
clean, and python3 docs/check.py passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@happycodelucky happycodelucky merged commit 66b5e3d into main Jun 12, 2026
3 checks passed
@happycodelucky happycodelucky deleted the happycodelucky/pedantic-poitras-57e090 branch June 12, 2026 03:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant