Java samples currently exist only in tutorial form.
- 2-space indent
- UTF-8 source encoding (declared in
pom.xml)
- Class/file:
PascalCaseverb-noun (e.g.,ManageCredentials.java); one public class per file - Variables:
camelCase(e.g.,issuerAddress,subjectFuture) - Constants:
UPPER_SNAKE_CASEforstatic final(e.g.,NETWORK_URL,FAUCET_URL,EXPLORER_BASE,CREDENTIAL_TYPE) - Package:
com.example.xrpl - Imports: two blocks separated by a blank line — all non-
java.*imports together (alphabetized:com.*,okhttp3.*,org.*, etc.), thenjava.*last. No wildcard imports.
Each code sample lives at _code-samples/<topic>/java/ as a self-contained Maven project:
_code-samples/<topic>/java/
├── README.md
├── pom.xml
├── target/ # Maven build output; gitignored
└── src/main/
├── java/com/example/xrpl/
│ └── <ClassName>.java # Tutorial samples (one class per user action)
└── resources/
└── logback.xmlRun any sample with mvn exec:java -Dexec.mainClass=com.example.xrpl.<ClassName> from the language root directory.
README.md is the entry point for a reader running the samples.
- Title:
# <Topic> Example (Java) - One-sentence description listing what the directory demonstrates
## Setupsection with anmvn installfenced block- One
##section per tutorial sample, in the order a reader should run them:
- Heading is a human-readable phrase for the action (e.g.,
## Manage Credentials,## Issue a Token) — not a code identifier like## ManageCredentials - Fenced
shblock withmvn exec:java -Dexec.mainClass=com.example.xrpl.<ClassName> - One-sentence summary of what the script will output
- Fenced
shblock showing actual expected console output (real addresses, tx hashes, JSON dumps, explorer links — captured from a successful sample code run)
---separator between tutorial sections
Java 11, UTF-8, single xrpl4j dependency, exec plugin for mvn exec:java:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>{topic}-samples</artifactId> <!-- e.g., credential-samples; change per directory -->
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.release>11</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.3.0</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.xrpl</groupId>
<artifactId>xrpl4j-client</artifactId>
<version>{latest-stable}</version>
</dependency>
</dependencies>
</project>src/main/resources/logback.xml quiets xrpl4j's DEBUG chatter so tutorial output stays readable:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Quiets xrpl4j's DEBUG chatter so tutorial output stays readable.
Raise xrpl4j to DEBUG to see wire-level transaction details. -->
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.xrpl.xrpl4j" level="WARN"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>xrpl4j sync client — org.xrpl.xrpl4j.client.XrplClient. Use CompletableFuture.supplyAsync + allOf().join() for parallel work (e.g., funding multiple accounts).
- Class-level Javadoc explaining what the sample demonstrates (and any preconditions, if applicable)
package com.example.xrpl;+ imports (alphabetical within groups,java.*last)- Class declaration with
NETWORK_URL,FAUCET_URL,EXPLORER_BASE, and tutorial-specific constants at top:
private static final HttpUrl NETWORK_URL = HttpUrl.get("https://s.altnet.rippletest.net:51234/");
private static final HttpUrl FAUCET_URL = HttpUrl.get("https://faucet.altnet.rippletest.net");
private static final String EXPLORER_BASE = "https://testnet.xrpl.org/transactions/";main()wrapsrun()and unwrapsCompletionExceptionso async failures print the same clean message as sync ones:
public static void main(String[] args) {
try {
run();
} catch (Exception e) {
// Unwrap CompletionException so async failures print the same clean message
// as sync failures. CompletableFuture.join() wraps exceptions in CompletionException
Throwable cause = (e instanceof CompletionException && e.getCause() != null)
? e.getCause() : e;
System.err.println("Error: " + cause.getMessage());
System.exit(1);
}
}private static void run()holds the main flow.- Connect to network and fund however many accounts the sample needs. Fund in parallel via
CompletableFuture.supplyAsync+allOf().join()when there's more than one. Two-account example:
// ----- Connect to Testnet and fund accounts -----
XrplClient xrplClient = new XrplClient(NETWORK_URL);
System.out.println("\n=== Funding issuer and subject accounts on Testnet ===\n");
CompletableFuture<KeyPair> issuerFuture = CompletableFuture.supplyAsync(
() -> createAndFundWallet(xrplClient));
CompletableFuture<KeyPair> subjectFuture = CompletableFuture.supplyAsync(
() -> createAndFundWallet(xrplClient));
CompletableFuture.allOf(issuerFuture, subjectFuture).join();
KeyPair issuer = issuerFuture.join();
KeyPair subject = subjectFuture.join();
Address issuerAddress = issuer.publicKey().deriveAddress();
Address subjectAddress = subject.publicKey().deriveAddress();
System.out.println("Issuer: " + issuerAddress);
System.out.println("Subject: " + subjectAddress);- Tutorial code steps.
- Useful helpers below a
// ===== Helper functions =====divider, each prefixed with a one-line comment. Copy any helpers the sample uses — the signatures and bodies below are canonical; only include the ones you call:
// ===== Helper functions =====
// Generates a new Ed25519 keypair, funds it from the Testnet faucet, and
// returns the keypair once the account is visible on a validated ledger.
private static KeyPair createAndFundWallet(XrplClient xrplClient) {
KeyPair keyPair = Seed.ed25519Seed().deriveKeyPair();
Address address = keyPair.publicKey().deriveAddress();
FaucetClient faucetClient = FaucetClient.construct(FAUCET_URL);
faucetClient.fundAccount(FundAccountRequest.of(address));
for (int attempt = 0; attempt < 20; attempt++) {
try {
xrplClient.accountInfo(AccountInfoRequestParams.builder()
.account(address)
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
.build());
return keyPair;
} catch (JsonRpcClientErrorException notYetVisible) {
try {
Thread.sleep(1_000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Account polling interrupted for " + address + ". " + e.getMessage(), e);
}
}
}
throw new IllegalStateException("Faucet funding for " + address + " did not confirm in time.");
}
// Fetches the next transaction sequence number of an address from
// the latest validated ledger.
private static UnsignedInteger accountSequence(XrplClient xrplClient, Address address) {
try {
AccountInfoResult info = xrplClient.accountInfo(AccountInfoRequestParams.builder()
.account(address)
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
.build());
return info.accountData().sequence();
} catch (JsonRpcClientErrorException e) {
throw new RuntimeException("Failed to fetch account sequence for " + address + ". " + e.getMessage(), e);
}
}
// Fetches the current network fee and returns the recommended fee for
// a standard (non-multisig, non-batch) transaction.
private static XrpCurrencyAmount recommendedFee(XrplClient xrplClient) {
try {
return FeeUtils.computeNetworkFees(xrplClient.fee()).recommendedFee();
} catch (JsonRpcClientErrorException e) {
throw new RuntimeException("Failed to fetch network fee. " + e.getMessage(), e);
}
}
// Computes a safe LastLedgerSequence for a new transaction. The
// latest validated ledger index plus a small buffer (20 ledgers).
private static UnsignedInteger lastLedgerSequence(XrplClient xrplClient) {
try {
UnsignedInteger validatedLedger = xrplClient.ledger(LedgerRequestParams.builder()
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
.build())
.ledgerIndexSafe()
.unsignedIntegerValue();
return validatedLedger.plus(UnsignedInteger.valueOf(20));
} catch (JsonRpcClientErrorException e) {
throw new RuntimeException("Failed to compute LastLedgerSequence. " + e.getMessage(), e);
}
}
// Prints a transaction as a formatted JSON.
private static void printTransactionJson(Transaction tx) {
try {
System.out.println(ObjectMapperFactory.create().writerWithDefaultPrettyPrinter().writeValueAsString(tx));
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to serialize transaction JSON. " + e.getMessage(), e);
}
}
// Signs and submits a transaction, then polls the network until
// the transaction reaches a validated state.
private static <T extends Transaction> TransactionResult<T> signSubmitAndWait(
XrplClient xrplClient,
KeyPair signer,
T transaction,
Class<T> transactionType
) {
SignatureService<PrivateKey> signatureService = new BcSignatureService();
UnsignedInteger lastLedgerSequence = transaction.lastLedgerSequence()
.orElseThrow(() -> new IllegalArgumentException(
"Must set LastLedgerSequence for polling expiration"));
try {
SingleSignedTransaction<T> signed = signatureService.sign(signer.privateKey(), transaction);
SubmitResult<T> submit = xrplClient.submit(signed);
if (!TransactionResultCodes.TES_SUCCESS.equals(submit.engineResult())) {
throw new IllegalStateException(
"Submission rejected. " + submit.engineResult() + " — " + submit.engineResultMessage());
}
Finality finality;
do {
Thread.sleep(1_000L);
finality = xrplClient.isFinal(
signed.hash(),
submit.validatedLedgerIndex(),
lastLedgerSequence,
transaction.sequence(),
signer.publicKey().deriveAddress()
);
} while (finality.finalityStatus() == FinalityStatus.NOT_FINAL);
if (finality.finalityStatus() != FinalityStatus.VALIDATED_SUCCESS) {
throw new IllegalStateException(
"Transaction failed with status " + finality.finalityStatus()
+ ". Result code: " + finality.resultCode().orElse("unknown"));
}
// Retrieve the transaction result; isFinal() only returns finality status
return xrplClient.transaction(
TransactionRequestParams.of(signed.hash()), transactionType);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Transaction polling interrupted. " + e.getMessage(), e);
} catch (JsonRpcClientErrorException | JsonProcessingException e) {
throw new RuntimeException("Transaction processing failed. " + e.getMessage(), e);
}
}
// Checks for a tesSUCCESS result code. If true, prints an explorer
// link. Otherwise, throws an error.
private static void requireSuccess(TransactionResult<?> result) {
String code = result.metadata().get().transactionResult();
String txType = result.transaction().transactionType().value();
if (!TransactionResultCodes.TES_SUCCESS.equals(code)) {
throw new IllegalStateException(txType + " failed with error code " + code);
}
System.out.println(txType + " succeeded!");
System.out.println("Explorer: " + EXPLORER_BASE + result.hash());
}- Before each major step, add a section comment and print a banner. Strict format:
// ----- Title -----on its own line, thenSystem.out.println("\n=== Title ===\n");immediately after. The title text should match between the two. - Build transactions with the builder pattern; always set
sequence,fee,lastLedgerSequence,signingPublicKeyfrom shared helpers, then print as pretty JSON:// ----- Prepare CredentialCreate transaction ----- System.out.println("\n=== Preparing CredentialCreate transaction ===\n"); CredentialCreate createTx = CredentialCreate.builder() .account(issuerAddress) .subject(subjectAddress) .credentialType(CREDENTIAL_TYPE) .sequence(accountSequence(xrplClient, issuerAddress)) .fee(recommendedFee(xrplClient)) .lastLedgerSequence(lastLedgerSequence(xrplClient)) .signingPublicKey(issuer.publicKey()) .build(); printTransactionJson(createTx); - Sign, submit, and wait via the shared
signSubmitAndWaithelper, then verify success withrequireSuccess:// ----- Sign, submit, and wait for CredentialCreate validation ----- System.out.println("\n=== Submitting CredentialCreate transaction ===\n"); TransactionResult<CredentialCreate> createResult = signSubmitAndWait( xrplClient, issuer, createTx, CredentialCreate.class); requireSuccess(createResult);