Local setup
Requires Java 11+ and Node.js 22+. The binding uses org.json:json:20250517, pinned in its Maven configuration. From the repository root on macOS/Linux:
cd sdk/java
sh setup.sh
The setup installs runtime npm dependencies, downloads and verifies the pinned JSON jar, and compiles the Java source and examples. It does not publish a package. Maven users can build locally with mvn package and install runtime/ dependencies separately.
Keep the runtime next to the deployed application; the JAR does not embed Node. No Maven Central publication is assumed.
Authorize
Use dev.pushnow.Client, java.nio.file.Path, org.json.JSONObject and org.json.JSONArray:
Client client = new Client(
Path.of("/absolute/path/to/sdk/java/runtime/main.js"),
trustedRootFingerprint, null);
JSONObject pending = client.beginAuthorization("https://api.pushnow.dev", "Java automation");
System.out.println(pending.getJSONObject("authorization").getString("user_code"));
System.out.println(pending.getString("fingerprint"));
JSONObject config = client.authorize(pending);
Verify the sender on the phone. The trusted account-root fingerprint must come from that trusted device, not the grant response. Save config privately and never print the whole object.
Prepare and retry
Client client = new Client(
Path.of("/absolute/path/to/sdk/java/runtime/main.js"),
trustedRootFingerprint, config);
JSONObject notification = new JSONObject()
.put("title", "Build complete")
.put("body", "Your report is ready.")
.put("pushEnabled", false)
.put("files", new JSONArray().put(new JSONObject()
.put("path", "/absolute/path/to/report.pdf")
.put("mime", "application/pdf")));
JSONObject envelope = client.prepare(notification);
// Save envelope privately before submitting it.
JSONObject result = client.retry(envelope);
Omit pushEnabled for alerts to all eligible devices. Set deviceIds to an array from the verified recipient directory to select devices, or an empty array for inbox-only. images, icon and links add content; scheduledAt and expiresAt select the delivery window.
The finalized sound input accepts default, silent or chime. Omit it for default behavior rather than passing JSONObject.NULL:
JSONObject notification = new JSONObject()
.put("title", "Quiet update").put("body", "Ready to review.")
.put("sound", "silent");
Sound is public routing metadata, while content and files remain encrypted. Silent still requests a visible alert; chime uses the new app's bundled asset. Arbitrary filenames and sound uploads are not accepted. iOS settings and Focus/DND apply without a critical-alert guarantee. This contract is being implemented; migration, deployment and audible-device verification are pending. See sound rollout status.
Runnable examples
After setting PUSHNOW_ROOT_FINGERPRINT independently, run from sdk/java:
java -cp target/test-classes:json-20250517.jar Example authorize
java -cp target/test-classes:json-20250517.jar Example send
These examples use private POSIX files and fail if the filesystem cannot enforce their permissions. On Windows, use Maven for compilation, ; in classpaths and a secret store or appropriate private ACLs instead of relying on these POSIX file examples.
send returns an object containing envelope and result; use prepare/save/retry for durable recovery. The example reuses its saved outbox on subsequent runs, so create a new outbox for a new notification.
Diagnostics
requestLogs() returns a copy of redacted request metadata. Failures use bounded IllegalStateException codes without raw server response content. Use one client per thread. Interrupting a request destroys the subprocess but may leave an uncertain HTTP result; resolve it by retrying the saved envelope.