Skip to content

Keep secrets

fakeivchenko edited this page Oct 9, 2026 · 1 revision

Tokens and passwords belong in the credential store of the system, which encrypts them for the user and keeps them from other users, and not in a file or a row of the store. In this guide, a mail application saves an API token, shows that it has one, and forgets it.

A token in the credential store, on Linux and on Windows 11

Save and read a secret

The secrets belong to the application by its name, so the application needs one:

public class Vault {
  public static void main(String[] args) {
    ApplicationParameters parameters = ApplicationParameters.builder().name("Mail").build();
    try (Application application = Application.create(parameters)) {
      Secrets secrets = application.secrets();
      application.bind(
          "saveToken",
          String.class,
          token -> {
            secrets.set("api-token", token);
            return null;
          });
      application.bind("savedToken", Void.class, _ -> secrets.get("api-token").orElse(null));
      application.bind("forgetToken", Void.class, _ -> secrets.delete("api-token"));
      application.show(
          WindowParameters.builder()
              .title("Mail")
              .size(new WindowSize(560, 380))
              .resource("secrets/index.html")
              .build());
      application.run();
    }
  }
}
Method What it does
set(key, value) Saves value under key, in place of the value that was there.
get(key) Returns the value, or an empty Optional when there is none.
delete(key) Deletes the value, and returns whether there was one.

A value takes at most Secrets.MAXIMUM_SIZE, 2560 bytes in UTF-8: the limit of the Credential Manager of Windows, kept on every platform, so that what works on one works on all. A longer value or a blank key throws IllegalArgumentException. If the system refuses, for example because the user didn't unlock the store, the call throws SecretStoreFailedException.

Keep the secret in Java

The page asks Java whether a token is saved, and shows only its end:

const show = (token) => {
  document.querySelector("#status").textContent = token === null
    ? "No token is saved."
    : `A token is saved: ${"•".repeat(8)}${token.slice(-4)}`;
};
window.savedToken().then(show);
document.querySelector("#save").addEventListener("submit", async (event) => {
  event.preventDefault();
  const token = document.querySelector("#token").value;
  await window.saveToken(token);
  document.querySelector("#token").value = "";
  show(await window.savedToken());
});

There is no lwjwae.secrets on the page, on purpose: a secret that the page can read is a secret that every script on the page can read. Bind what the page needs, such as a call that signs a request with the token, and keep the token itself in Java.

Platforms

Platform Where the secret goes
Windows A generic credential of the Credential Manager, named APPLICATION/KEY, such as Mail/api-token.
macOS A generic password of the login keychain, with the application as the service and the key as the account.
Linux A password of the Secret Service, such as GNOME Keyring or KWallet, through libsecret, with the application and the key as its attributes.

Good to know

  • A call blocks until the system answers, and the system can first ask the user to unlock the store. A handler of a binding runs on a virtual thread of its own, so it can call it; on the UI thread, don't.
  • On Linux, the desktop needs a running Secret Service. GNOME and KDE Plasma start one with the session; a minimal window manager might not.
  • On Windows, cmdkey /list:Mail* lists the secrets of the application, and Credential Manager in the Control Panel shows them under Windows Credentials.

Clone this wiki locally