Commitment levels define how finalized a transaction or account state is on the Solana blockchain. Choosing the right commitment level is crucial for balancing speed, consistency, and security.
Overview
The Commitment enum provides three levels of transaction finality:
Package: software.sava.rpc.json.http.request
File: Commitment.java:7
public enum Commitment {
FINALIZED("finalized"),
CONFIRMED("confirmed"),
PROCESSED("processed")
}
See Commitment.java:9-11.
Commitment Levels
PROCESSED
Query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout, meaning the cluster has recognized this block as processed
Characteristics:
- Fastest - Data available immediately
- Least secure - May be rolled back
- Use case: Real-time updates, mempool monitoring, UI previews
Risk: Blocks at this level can still be orphaned due to network forks. The transaction may not be included in the final chain.
Example:
import software.sava.rpc.json.http.request.Commitment;
// Monitor pending transactions
var signatures = client.getSignaturesForAddress(
Commitment.PROCESSED,
address,
10
).join();
for (var sig : signatures) {
System.out.println("Pending tx: " + sig.signature());
}
CONFIRMED
Query the most recent block that has been voted on by supermajority of the cluster
Characteristics:
- Moderate speed - Usually available within 1-2 seconds
- Good security - Unlikely to be rolled back
- Use case: Most applications, wallet confirmations, general queries
- Default - Recommended for most use cases
Risk: Very low probability of rollback. Only extreme network conditions could cause this.
Example:
// Default commitment for production apps
var client = SolanaRpcClient.build()
.endpoint(URI.create("https://api.mainnet-beta.solana.com"))
.defaultCommitment(Commitment.CONFIRMED)
.createClient();
// Check wallet balance
var balance = client.getBalance(walletAddress).join();
System.out.println("Balance: " + balance.lamports() + " lamports");
FINALIZED
Query the most recent block confirmed by supermajority of the cluster as having reached maximum lockout (cannot be rolled back)
Characteristics:
- Slowest - May take 30-45 seconds
- Most secure - Cannot be rolled back
- Use case: High-value transactions, bridges, exchanges, auditing
- Guarantee: Absolute finality, transaction is permanent
Risk: None. This is the final state of the blockchain.
Example:
// Wait for absolute finality before crediting account
var txSig = client.sendTransaction(transaction, feePayer).join();
// Poll for finalized status
var startTime = System.currentTimeMillis();
while (true) {
var status = client.getSignatureStatuses(List.of(txSig)).join().get(txSig);
if (status.confirmationStatus() == Commitment.FINALIZED) {
if (status.error() == null) {
System.out.println("Transaction finalized successfully!");
creditUserAccount();
} else {
System.err.println("Transaction failed: " + status.error());
}
break;
}
Thread.sleep(1000);
if (System.currentTimeMillis() - startTime > 60_000) {
System.err.println("Timeout waiting for finalization");
break;
}
}
Comparison Table
| Level | Speed | Security | Rollback Risk | Typical Delay |
|---|
| PROCESSED | Fastest | Lowest | Medium-High | <400ms |
| CONFIRMED | Fast | High | Very Low | 1-2s |
| FINALIZED | Slowest | Highest | None | 30-45s |
When to Use Each Level
Use PROCESSED for:
- Real-time UI updates
- Live account balance displays
- Transaction status polling (initial check)
- Mempool monitoring
- Non-critical data display
// Real-time balance display
var ws = SolanaRpcWebsocket.build()
.uri(SolanaNetwork.MAIN_NET)
.commitment(Commitment.PROCESSED)
.create();
ws.accountSubscribe(walletAddress, accountInfo -> {
updateUI(accountInfo.lamports());
});
Use CONFIRMED for:
- Standard wallet operations
- NFT minting confirmations
- General-purpose queries
- Most dApp interactions
- Default application behavior
// Standard dApp usage
var client = SolanaRpcClient.build()
.defaultCommitment(Commitment.CONFIRMED)
.createClient();
// Get account info for display
var accountInfo = client.getAccountInfo(publicKey).join();
displayAccountData(accountInfo);
Use FINALIZED for:
- Exchange deposits/withdrawals
- Bridge operations
- Large value transfers
- Irreversible operations
- Compliance/audit requirements
- Settlement finality
// Bridge deposit - require finality
public void processDeposit(String txSig) {
var status = client.getSignatureStatuses(
List.of(txSig)
).join().get(txSig);
if (status.confirmationStatus() == Commitment.FINALIZED
&& status.error() == null) {
// Safe to credit on destination chain
bridgeToEthereum(txSig);
}
}
Best Practices
Progressive Confirmation
Use multiple commitment levels for better UX:
public void monitorTransaction(String txSig) {
// 1. Show as pending immediately (PROCESSED)
var ws = SolanaRpcWebsocket.build()
.commitment(Commitment.PROCESSED)
.create();
ws.signatureSubscribe(txSig, result -> {
ui.showStatus("Transaction received");
});
// 2. Show as confirmed (CONFIRMED)
ws.signatureSubscribe(
Commitment.CONFIRMED,
txSig,
result -> {
ui.showStatus("Transaction confirmed");
enableRelatedActions();
}
);
// 3. Show as finalized (FINALIZED)
ws.signatureSubscribe(
Commitment.FINALIZED,
txSig,
result -> {
ui.showStatus("Transaction finalized");
markAsPermanent();
}
);
}
Default to CONFIRMED
Set CONFIRMED as default and override when needed:
var client = SolanaRpcClient.build()
.defaultCommitment(Commitment.CONFIRMED)
.createClient();
// Use default (CONFIRMED)
var balance = client.getBalance(address).join();
// Override for high-value check (FINALIZED)
var finalizedBalance = client.getBalance(
Commitment.FINALIZED,
treasuryAddress
).join();
// Override for real-time updates (PROCESSED)
var liveBalance = client.getBalance(
Commitment.PROCESSED,
userAddress
).join();
Handle Commitment in WebSocket Subscriptions
var ws = SolanaRpcWebsocket.build()
.uri(SolanaNetwork.MAIN_NET)
.commitment(Commitment.CONFIRMED) // Default
.create();
// Use default CONFIRMED
ws.accountSubscribe(account, updates -> {
displayToUser(updates);
});
// Override with PROCESSED for real-time
ws.accountSubscribe(
Commitment.PROCESSED,
speedAccount,
liveUpdates -> {
updateChart(liveUpdates);
}
);
// Override with FINALIZED for security
ws.accountSubscribe(
Commitment.FINALIZED,
vaultAccount,
secureUpdates -> {
auditLog(secureUpdates);
}
);
Implementation Details
getValue()
Get the string value for JSON-RPC requests:
Commitment commitment = Commitment.CONFIRMED;
String value = commitment.getValue(); // Returns "confirmed"
This value is used in JSON-RPC request payloads. See Commitment.java:19-21.
Parsing Commitment
The enum includes a parser for deserializing from JSON responses:
public static final CharBufferFunction<Commitment> PARSER
See Commitment.java:23-33.
Common Patterns
Transaction Confirmation Flow
public class TransactionMonitor {
private final SolanaRpcClient client;
private final SolanaRpcWebsocket ws;
public void sendAndMonitor(Transaction tx, Signer signer) {
// 1. Send transaction
String txSig = client.sendTransaction(tx, signer).join();
System.out.println("Transaction sent: " + txSig);
// 2. Wait for PROCESSED (immediate feedback)
ws.signatureSubscribe(
Commitment.PROCESSED,
txSig,
result -> System.out.println("✓ Processed")
);
// 3. Wait for CONFIRMED (safe for most purposes)
ws.signatureSubscribe(
Commitment.CONFIRMED,
txSig,
result -> {
System.out.println("✓ Confirmed");
if (result.error() == null) {
notifyUser("Transaction successful!");
}
}
);
// 4. Wait for FINALIZED (absolute certainty)
ws.signatureSubscribe(
Commitment.FINALIZED,
txSig,
result -> {
System.out.println("✓ Finalized");
if (result.error() == null) {
recordInDatabase(txSig);
}
ws.signatureUnsubscribe(Commitment.FINALIZED, txSig);
}
);
}
}
Account Consistency Check
public void verifyAccountConsistency(PublicKey account) {
// Get data at different commitment levels
var processed = client.getAccountInfo(
Commitment.PROCESSED,
account
).join();
var confirmed = client.getAccountInfo(
Commitment.CONFIRMED,
account
).join();
var finalized = client.getAccountInfo(
Commitment.FINALIZED,
account
).join();
// Compare states
System.out.println("Processed slot: " + processed.context().slot());
System.out.println("Confirmed slot: " + confirmed.context().slot());
System.out.println("Finalized slot: " + finalized.context().slot());
if (processed.lamports() != finalized.lamports()) {
System.out.println("⚠️ Balance differs between PROCESSED and FINALIZED");
}
}
Security Considerations
Never Use PROCESSED for Financial Operations
// ❌ WRONG - Deposits could be rolled back
public void processDeposit(String txSig) {
var status = client.getSignatureStatuses(
List.of(txSig)
).join().get(txSig);
if (status.confirmationStatus() == Commitment.PROCESSED) {
creditUserAccount(); // DANGEROUS!
}
}
// ✅ CORRECT - Wait for finality
public void processDeposit(String txSig) {
var status = client.getSignatureStatuses(
List.of(txSig)
).join().get(txSig);
if (status.confirmationStatus() == Commitment.FINALIZED
&& status.error() == null) {
creditUserAccount(); // Safe
}
}
Bridge Safety
public class BridgeValidator {
private static final Commitment REQUIRED_COMMITMENT = Commitment.FINALIZED;
public boolean canBridgeTransaction(String txSig) {
var status = client.getSignatureStatuses(
List.of(txSig)
).join().get(txSig);
return status.confirmationStatus() == REQUIRED_COMMITMENT
&& status.error() == null;
}
}
See Also