Skip to content

java.net host identity, and a real java.util.Properties - #704

Merged
yogthos merged 5 commits into
mainfrom
feat/java-net-host-identity
Aug 22, 2026
Merged

yogthos merged 5 commits into
mainfrom
feat/java-net-host-identity

Conversation

@yogthos

@yogthos yogthos commented Aug 22, 2026

Copy link
Copy Markdown
Member

Closes jolt-6227.

The bead was filed from clj-uuid's comments; reading its node.clj showed those comments describe the approach it rejects. make-node-id never calls .getHardwareAddress — it digests the machine's identity strings instead. So the gap is different from what was filed, and this implements what the code actually reaches for.

java.net

InetAddress/getLocalHost, getAllByName (an array, so alength/aget hold), .getCanonicalHostName, .getAddress, .isLoopbackAddress, .equals/.hashCode; and a new java.net.NetworkInterface — getNetworkInterfaces, getByName, getByInetAddress, .getInetAddresses, .getHardwareAddress, .getDisplayName, .isLoopback — over getifaddrs(3), gethostname(2) and getnameinfo(3). IPv4, matching the rest of jolt.socket.

getifaddrs reports one entry per address, so an interface with an address and a MAC appears twice; the entries are grouped by name into one interface each, which is the shape java.net presents. An address read off an interface carries no hostname — the JVM prints it /127.0.0.1 — and .getHostName resolves once and caches, rather than putting a DNS round trip per address in the way of enumerating interfaces.

Touching any java.net class now loads jolt.socket on demand, the courtesy java.time and MessageDigest already had. A JVM library reaching for InetAddress should no more have to know which namespace installs it than it does on the JVM.

.getHardwareAddress and getByInetAddress are here even though clj-uuid doesn't use them: they're the documented java.net surface, and the getifaddrs walk that answers the rest already has the data.

java.util.Properties

Found along the way, and needed for the same caller: System/getProperties returned a plain map, so .getProperty read as a key lookup and answered nil for every system property. There's now a real Properties with the JVM's defaults chain — and its exact span: getProperty, propertyNames and stringPropertyNames see the defaults; the inherited Hashtable surface (get, containsKey, keySet, values, size) does not. A Properties holding only defaults answers isEmpty true with an empty keySet while propertyNames lists them.

System/getProperties holds its values as real entries, as on the JVM, rebuilt per call so user.dir and java.class.path stay current instead of freezing at the first call, and writes through to the store System/getProperty reads.

While in there, the three spellings of "is this a hashmap-backed host object" were one predicate's worth of drift; they are one now, so a shim of that shape cannot be countable through one accessor and opaque through another.

Validation

  • make test green: exit 0, 82 CI targets, 0 new divergences, 0 failures
  • 30 new checks in the jolt.socket gate (make smoke greps its marker), each expectation certified against JVM Clojure 1.12.5 first — including the defaults-span split and the lazy-resolve-and-cache behaviour
  • 4 cold-process smoke cases for the autoload path (a fresh process touching java.net with no require)
  • InetAddress/getLocalHost renders byte-identically to the JVM on this machine
  • clj-uuid's all-local-addresses + make-node-id run end to end and are stable across calls; the config library's (->> (System/getProperties) (map (fn [[k v]] …))) still resolves
  • Site docs updated (host-interop.md) — the "gated behind (require 'jolt.socket)" note is no longer true

Yogthos added 5 commits August 22, 2026 16:02
InetAddress grew getLocalHost, getAllByName, .getCanonicalHostName and
.getAddress, and java.net.NetworkInterface is new: getNetworkInterfaces,
getByName, getByInetAddress, .getInetAddresses, .getHardwareAddress,
.getDisplayName, over getifaddrs(3). IPv4, like the rest of jolt.socket.
getifaddrs reports one entry per address, so the entries are grouped by name
into one interface each, which is the shape java.net presents.

Touching any java.net class now loads jolt.socket on demand, the courtesy
java.time and MessageDigest already had. A JVM library reaching for
InetAddress should no more have to know which namespace installs it than it
does on the JVM.

System/getProperties returned a plain map, which answered .getProperty as a
key lookup and so read nil for every system property — a library that reads
them through the Properties API saw nothing. There is now a java.util.Properties
with the defaults chain the JVM's has: it shares the override table, so a
setProperty through it is what System/getProperty reports, and the values jolt
computes on read (user.dir, java.class.path) are its defaults rather than
frozen entries, so they stay current.

The three spellings of "is this a hashmap-backed jhost" are one predicate now.
They had drifted, which is why Properties was countable but not seqable until
the map view went through one accessor.
…sses resolve lazily

Two corrections to the previous commit, both found by checking the reference
rather than assuming.

java.util.Properties: the JVM is precise about which operations span the
defaults chain. getProperty, propertyNames and stringPropertyNames do; the
inherited Hashtable surface — get, containsKey, keySet, values, size — does
not. A Properties holding only defaults answers isEmpty true and an empty
keySet while its propertyNames lists them. The whole Map surface had been made
defaults-aware, which is a superset the JVM does not have.

That leaves System/getProperties, whose values must be visible to get and count
because on the JVM they are real entries. It now holds them as real entries,
rebuilt per call so user.dir and java.class.path stay current, and writes
through to the override store so a setProperty is what System/getProperty
reports.

NetworkInterface no longer reverse-resolves every address as it enumerates.
The JVM leaves an address read off an interface unresolved — its toString is
"/127.0.0.1" — and resolves on the first .getHostName, caching. Resolving
eagerly put a DNS round trip per address in the way of listing interfaces.
…lts chain nests

A 30-case differential run against the reference turned up two divergences.

setProperty consulted the defaults for the value it returns. On the JVM it
delegates to Hashtable.put, so it reports what THIS object held — nil when the
key was only ever in the defaults — not the value it is now shadowing.

A Properties whose defaults is itself a Properties searched only that object's
own table, because the defaults were read with a plain map lookup and the Map
surface is defaults-blind. getProperty and propertyNames now recurse down the
chain, as the JVM's recursive defaults.getProperty does.

The three cases are pinned in the jolt.socket gate.
Changelog only: main's Unreleased entries (rounds 1 and 4, the interface-tag
dispatch fix) and this branch's meet in one Added and one Fixed section.
Changelog only: the FFI entries join this branch's under one Added and one
Fixed section.
@yogthos
yogthos merged commit 11cc1eb into main Aug 22, 2026
6 checks passed
@yogthos
yogthos deleted the feat/java-net-host-identity branch August 22, 2026 22:27
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