SuperJ Community Edition. Free for personal use, open source projects, non-profits, educational institutions, and small businesses (< $5M annual revenue).
curl -fsSL https://superj.dev/releases/v1.3/install.sh | sh -s -- https://superj.dev/releases/v1.3
This installs SuperJ to ~/superj and adds it to your PATH. Available on macOS arm64, Linux x86_64, and Linux arm64.
NioAdapter.connectTls now requires a verification policy (#3365)**If you use TLS as a client, this release will not compile-and-run your code unchanged, and that is
deliberate.** connectTls(host, port, provider, null) now throws.
The previous release's notes said the TLS client did not authenticate the server. It now does — and the
fix is not only new checks, it is removing the way you could get an unauthenticated connection without
asking for one. tlsConfig = null meant "anonymous client" — no client certificate — and silently
also meant "do not verify the server". One spelling for two unrelated statements, so callers got the
second without choosing it.
Pick one:
// Pin the server's public key (64 bytes X||Y of its P-256 point).
Pem leaf = Pem.parse(certPem, 0, certPem.length);
byte[] pin = new byte[64];
X509.spkiEcPoint(leaf.der, 0, leaf.der.length, pin, 0);
adapter.connectTls(host, port, provider, new TlsConfig().pinnedPeerKey(pin));
// Or anchor on a CA you supply, and require the hostname the certificate covers.
byte[] caDer = Pem.parse(caPem, 0, caPem.length).der;
adapter.connectTls(host, port, provider,
new TlsConfig().trustAnchor(caDer).expectHost("api.internal"));
// Or keep the old behaviour — encrypted, accepts any man-in-the-middle — explicitly.
adapter.connectTls(host, port, provider, new TlsConfig().insecureSkipVerify(true));
Pin an endpoint you control; anchor a service that rotates its leaf, since a pin breaks on rotation.
insecureSkipVerify(true) is a legitimate choice for a loopback test or a network where interception is
already in your threat model — it is a named method precisely so that choice is visible in your code.
What is verified. Pin equality or a verifiable path to your CA; issuer/subject compared as raw DER;
an ecdsa-with-SHA256 signature over each certificate's tbsCertificate; cA=TRUE on the anchor and
every intermediate (absent counts as FALSE, RFC 5280 §4.2.1.9); the validity window of every
certificate on the path; the CertificateVerify signature over Transcript-Hash(CH..Certificate); and
subjectAltName dNSNames when expectHost is set, with wildcards matching exactly one leftmost label and
no fallback to the Common Name (RFC 2818).
Failures throw sj.crypto.TlsPeerUnverifiedException and print the reason to stderr even with no logger
configured — a silent close is indistinguishable from a network drop, and invisible rejections teach
people that verification is flaky.
Still not a public-web client, though much closer. RSA verification (PKCS#1 v1.5 and PSS), a
multi-anchor trust store, wider signature_algorithms, and a small bundle of real public roots all
landed this cycle — the pieces that were previously missing. It still does not work end to end against an
arbitrary HTTPS host: measured on this build, example.com:443 and superj.dev:443 both fail to
complete, and not at the verification step. The ClientHello sends no SNI (#3387), so a
virtual-hosted server cannot select a certificate in the first place. Until that ships, this
authenticates your own services, where you name the anchor.
openTls (the server role) is unaffected.
modular exponentiation, RSASSA-PKCS1-v1_5-VERIFY (SHA-256/384/512), RSASSA-PSS-VERIFY,
SPKI RSA key parsing, RSA-signed chain links, a wider ClientHello signature_algorithms, and
TlsConfig.trustAnchors(byte[][]) for a multi-anchor store. Pem.certBundle(pem) splits a PEM
bundle into DER anchors; a small set of real public roots ships at sdk/net/ca_bundle.pem.
See the SNI caveat above before pointing this at a public host.
Pem.certDer(pem) turns the PEM you have into the DER trustAnchor takes, in one call (#3366). SessionHandler.peerCertificate() hands the application the peer's leaf before
onConnected(), for policy the engine does not do — a fingerprint pin, an OID check, an audit
log. It is not where verification happens.
connect() to a dead port now reports failure (#3369). It fired onConnected() and then went silent, so a client could not tell "connecting" from "never will". SO_ERROR is probed on
connecting sockets and the failure routes to onDisconnected(). This is the regression #2695 was
filed for.
nanoTime, so they can pass(#3370). They had been permanently red, which is what hid #3369.
WebClient.sj and hand-written clients could connect and then receive nothing: a non-blocking connect() returns before the handshake completes, so the first
request write blocks and must be retried on the writable event.
HttpResponseListener.onError(int) is now documented — the code is always
HttpResponseReader.ERROR (2), covering a parse failure, an over-large response, or a disconnect
without a response — along with the fact that a deterministic onError(2) on the first poll means the
request was never sent.
strict arena reaches the same verdict on every edition (#3361). Each SDK now ships build/escape-summaries.txt, so --sdk-path answers as --sdk-source does and superj check agrees
with build. Also fixes the loader looking one directory too high for any --sdk-path containing a
slash, which made this and the capability closure below inert for every installed user.
graph rather than a hand-maintained list of types. Previously such a grant could be reported as
"never used — consider removing".
superj memlog reports arena reclaim per block (#3364) — the question W_ARENA_NO_SAVINGS cannotanswer, since it warns only when every allocation in a block escapes. Note that scoping is lexical:
an object whose constructor allocates its own payload leaves only the headers in the block. Measured
on the same 819 KB, 983040 bytes reclaimed when allocated directly versus 65536 via a constructor.
Process.run(String[], String, int) no longer hangs (#3363). The pipe-drain loop called poll() with an uninitialized .events field, so it watched for whatever the caller's stack frame happened to
contain; when that did not include POLLIN it spun forever.
tests/tls_certs/: a certificate with a real subjectAltName, and a CA plus CA-signed leaf that openssl verify accepts.
E_ARENA_ESCAPE and W_ARENA_NO_SAVINGS no longer carry the retracted claim that the two SDK build modes can disagree, and W_ARENA_NO_SAVINGS records that its threshold is a count of
allocation sites rather than bytes (#3360/#3364).
onConnected(), letting the application dispatch between HTTP/1.1 and HTTP/2.superj memlog subcommand for debugging memory issues; --mem-track compile flag arms the arena event log; per-type histogram under SJ_MEM_DEBUG.libsuperj_sdk.a and sdk.ll so superj test works without --enterprise.emitItable now declares default-method symbols for external (SDK) interfaces, so user code implementing an SDK interface with default methods links cleanly against the archive.byte[] tls13Prefix = { 't','l','s','1','3',' ' } were miscompiled into 16/32-bit slots, corrupting the TLS 1.3 HKDF label prefix and breaking OpenSSL interop. Fixed by routing field initializers through checkFieldInit and narrowing arrayInitType to the declared element type.pemCertToDer parsed only the first PEM block; non-browser clients without AIA (openssl s_client, minimal-CA curl, Java SSL) failed to verify. setCertificateChain now iterates all PEM blocks and sendCertificate emits the complete certificate_list.@VisibleForTesting cross-package access under --test, and tests/tls_interop.sh now runs on Linux (auto-detects OS/arch; previously macOS-only).putAscii that rejects out-of-range ASCII instead of clamping — for code paths where silent clamping would mask a bug.@VisibleForTesting (#2718): first-class annotation for cross-package private access under --test, so tests can reach package-private fields without adding public accessors.runtime/proc.c replaced obsolescent usleep with nanosleep (#2733) — the old call broke Linux builds under clang-22's strict C99 (usleep requires _DEFAULT_SOURCE on glibc, not _POSIX_C_SOURCE).a: b: for …), break L / continue L, labels on if/do/try/foreach, finally-unwind on labeled break/continue. 34 ported conformance tests (JLS/OpenJDK/ECJ style) + 7 finally-interaction cases.@Test method now runs just that test (was: ran every test in the file). The synthesized test-runner main now marshals argv so --filter <methodname> reaches the TestRunner.getSession().stop() now runs on the EDT; process kill is skipped when the process already exited. End-to-end lldb test added to make test-debug.