solana-sdkj is a modern, developer-friendly SDK for building Java applications on the Solana blockchain.
It provides complete RPC access, convenient abstractions for System Programs, and first-class support for Borsh-encoded program states using struct-layout.
- Full support for all Solana RPC methods
- Built-in abstractions for SystemProgram, Transactions, Program Derived Addresses, and VersionedMessage (v0)
- Strong Borsh/Rust/C state decoding via
@StructLayout
Easily map program account data into structured Java classes
Extend theStateclass and pair it withgetAccountState()to decode any base64-encoded account with full type safety - Safer and more maintainable than
jsonParsed— no runtime casting, no fragile field access. - Clean interoperability with custom programs and PDA-based layouts
- Java 17+ compatible, with secure cryptography powered by BouncyCastle
solana-sdkj depends on the following libraries:
- OkHttp – For making HTTP requests.
- Moshi – JSON serialization/deserialization library.
- BouncyCastle – For cryptographic operations.
- Struct-layout - For Borsh data.
You can include solana-sdkj in your project via Maven. Add the following dependency to your pom.xml:
<dependency>
<groupId>net.deanly</groupId>
<artifactId>solana-sdkj</artifactId>
<version>0.1.2</version>
</dependency>If you're using Gradle, add the following to your build.gradle:
implementation 'net.deanly:solana-sdkj:0.1.2'solana-sdkj lets you easily interact with Solana through the RPC API and built-in program abstractions. Here is a quick example:
import net.deanly.solana.sdk.rpc.client.RpcClient;
import net.deanly.solana.sdk.rpc.client.config.ClientConfig;
import net.deanly.solana.sdk.rpc.client.config.Network;
import net.deanly.solana.sdk.types.PublicKey;
public class Example {
public static void main(String[] args) throws Exception {
RpcClient rpcClient = new RpcClient(ClientConfig.builder()
.network(Network.DEVNET)
.build());
// Example: Retrieve Balance
PublicKey publicKey = new PublicKey("YourPublicKeyHere");
long balance = rpcClient.getRpcHttpApi().getBalance(publicKey);
System.out.println("Balance: " + balance);
}
}This example demonstrates how to transfer SOL (measured in Lamports) from one wallet to another.
import net.deanly.solana.sdk.crypto.KeyPair;
import net.deanly.solana.sdk.crypto.PublicKey;
import net.deanly.solana.sdk.program.core.system.SystemProgram;
import net.deanly.solana.sdk.rpc.client.RpcClient;
import net.deanly.solana.sdk.rpc.client.config.ClientConfig;
import net.deanly.solana.sdk.rpc.client.config.Network;
import net.deanly.solana.sdk.transaction.Transaction;
public class TransferExample {
public static void main(String[] args) {
// Configure Solana RPC
RpcClient rpcClient = new RpcClient(ClientConfig.builder().network(Network.DEVNET).build());
try {
// Account setup
PublicKey senderPublicKey = new PublicKey("YOUR_SENDER_PUBLIC_KEY");
PublicKey receiverPublicKey = new PublicKey("YOUR_RECEIVER_PUBLIC_KEY");
KeyPair senderKeyPair = new KeyPair("YOUR_SENDER_PRIVATE_KEY_IN_BASE58".getBytes());
// Amount to transfer (in lamports, 1 SOL = 10^9 lamports)
long lamports = 1000000L;
// Build transaction
Transaction transaction = new Transaction();
transaction.addInstruction(
SystemProgram.transfer(senderPublicKey, receiverPublicKey, lamports)
);
transaction.setSigner(senderKeyPair);
// Send transaction
String transactionSignature = rpcClient.getRpcHttpApi().sendTransaction(transaction).getValue();
System.out.println("Transaction sent successfully! Signature: " + transactionSignature);
} catch (Exception e) {
System.err.println("Failed to complete transfer: " + e.getMessage());
}
}
}Use the simulateTransaction RPC method to test the validity of a Solana transaction without executing it on-chain.
import net.deanly.solana.sdk.rpc.client.RpcClient;
import net.deanly.solana.sdk.rpc.client.config.ClientConfig;
import net.deanly.solana.sdk.rpc.client.config.Network;
import net.deanly.solana.sdk.crypto.KeyPair;
import net.deanly.solana.sdk.crypto.PublicKey;
import net.deanly.solana.sdk.transaction.Transaction;
import net.deanly.solana.sdk.transaction.instruction.TransactionInstruction;
import net.deanly.solana.sdk.program.core.system.SystemProgram;
import net.deanly.solana.sdk.types.Blockhash;
import net.deanly.solana.sdk.rpc.request.config.SimulateTransactionConfig;
import net.deanly.solana.sdk.rpc.response.ResValueSimulatedTransaction;
import net.deanly.structlayout.StructLayout;
public class SimulateTransactionExample {
public static void main(String[] args) {
// Configure Solana RPC
RpcClient rpcClient = new RpcClient(ClientConfig.builder().network(Network.DEVNET).build());
try {
// Fetch recent blockhash
Blockhash recentBlockhash = rpcClient.getRpcHttpApi()
.getLatestBlockhash()
.getValue()
.getBlockhash();
// Account setup
PublicKey senderPublicKey = new PublicKey("YOUR_SENDER_PUBLIC_KEY");
PublicKey receiverPublicKey = new PublicKey("YOUR_RECEIVER_PUBLIC_KEY");
KeyPair senderKeyPair = new KeyPair("YOUR_SENDER_PRIVATE_KEY_IN_BASE58".getBytes());
// Transaction setup
long lamports = 100000L; // Amount in lamports
Transaction transaction = new Transaction();
transaction.setRecentBlockhashForCompile(recentBlockhash);
transaction.addInstruction(
SystemProgram.transfer(senderPublicKey, receiverPublicKey, lamports)
);
transaction.sign(senderKeyPair);
// Simulate transaction
var config = SimulateTransactionConfig.builder().skipPreflight(true).build();
ResValueSimulatedTransaction simulateResponse = rpcClient.getRpcHttpApi()
.simulateTransaction(transaction, config).getValue();
// Print results
StructLayout.debug(transaction);
System.out.println("Simulation Result: " + simulateResponse);
} catch (Exception e) {
System.err.println("Simulation failed: " + e.getMessage());
}
}
}Sample Output
Order Field Offset Bytes (HEX)
=======================================================================
1[].length signatures 0000000 01
1[0] signatures 0000001 39 CD A1 D0 56 A4 08 6B 63 D6 F3 BA 0C FA 32 F2 F9 35 BE 7F 87 F4 10 C4 26 45 B1 AE 26 0D A9 21 9C D3 8A 0B 45 E9 F6 C7 B3 65 F6 08 A4 0E 2B 85 A7 71 0B 3E 73 05 58 DE 66 92 E6 69 4A 73 AC 05
2-1-1 numRequiredSignatures 0000065 01
2-1-2 numReadonlySignedAccounts 0000066 00
2-1-3 numReadonlyUnsignedAccounts 0000067 01
2-2[].length staticAccountKeys 0000068 03
2-2[0] staticAccountKeys 0000069 B8 A3 A3 BD 09 5C 2D F5 D4 68 BE 74 E4 A8 29 C4 F2 52 44 77 79 3D 52 19 29 EE 73 C3 43 84 41 F2
2-2[1] staticAccountKeys 0000101 22 6B FA 70 D4 05 93 83 03 CA 98 BD B5 A2 BA FA 8C 47 4D B5 38 FE 1B F2 5A 65 49 7F A8 3F DF F7
2-2[2] staticAccountKeys 0000133 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
2-3 recentBlockhash 0000165 50 29 81 53 E9 3F 5F D5 A7 38 A4 FF 3F AA AF 4E 4A 27 70 BA 70 6F 41 97 47 C1 F0 11 A3 88 D5 27
2-4[].length instructions 0000197 01
2-4[0]-1 programIdIndex 0000198 02
2-4[0]-2[].length accountKeyIndexes 0000199 02
2-4[0]-2[0] accountKeyIndexes 0000200 00
2-4[0]-2[1] accountKeyIndexes 0000201 01
2-4[0]-3 data 0000202 0C 03 00 00 00 40 42 0F 00 00 00 00 00
=======================================================================
Total Bytes: 215
Simulation Result: ResValueSimulatedTransaction(err=null, logs=[Program 11111111111111111111111111111111 invoke [1], Program 11111111111111111111111111111111 success], accounts=null, unitsConsumed=150, returnData=null, innerInstruction=[])
The simulateTransaction function allows developers to test transactions before committing them to the blockchain. The result will indicate if the transaction would succeed or fail and, if it fails, what issues to address.
You can decode a Solana account’s data into a structured class (State) using getAccountState. This makes it easy to work with Borsh-encoded on-chain data, such as Metaplex’s Token Metadata.
import net.deanly.solana.sdk.rpc.client.RpcClient;
import net.deanly.solana.sdk.rpc.client.config.ClientConfig;
import net.deanly.solana.sdk.rpc.client.config.Network;
import net.deanly.solana.sdk.types.PublicKey;
import net.deanly.solana.sdk.types.ProgramDerivedAddress;
import net.deanly.solana.sdk.rpc.response.ResValueAccountInfo;
import net.deanly.solana.sdk.rpc.request.config.AccountInfoConfig;
import net.deanly.solana.sdk.types.Encoding;
import net.deanly.solana.sdk.program.metaplex.tokenmetadata.state.TokenMetadataState;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class ReadTokenMetadataExample {
public static void main(String[] args) throws Exception {
RpcClient rpcClient = new RpcClient(ClientConfig.builder()
.network(Network.MAINNET)
.build());
// Target Mint Address
String mintBase58 = "6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN";
PublicKey mint = PublicKey.valueOf(mintBase58);
// Metaplex Token Metadata Program ID
PublicKey metadataProgramId = TokenMetadataProgram.PROGRAM_ID;
// Derive Metadata PDA
List<byte[]> seeds = List.of(
"metadata".getBytes(StandardCharsets.UTF_8),
metadataProgramId.toByteArray(),
mint.toByteArray()
);
ProgramDerivedAddress pda = PublicKey.findProgramAddress(seeds, metadataProgramId);
System.out.println("Metadata PDA: " + pda.getAddress().toBase58());
// Fetch and decode state directly
TokenMetadataState state = rpcClient.getRpcHttpApi().getAccountState(pda.getAddress(), TokenMetadataState.class);
System.out.println("Decoded Token Metadata:");
System.out.println(state);
}
}Sample Output
TokenMetadataState(
key=4,
updateAuthority=5e2qRc1DNEXmyxP8qwPwJhRWjef7usLyi7v5xjqLr5G7,
mint=6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN,
name=OFFICIAL TRUMP,
symbol=TRUMP,
uri=https://arweave.net/cSCP0h2n1crjeSWE9KF-XtLciJalDNFs7Vf-Sm0NNY0,
sellerFeeBasisPoints=0,
creators=[...],
primarySaleHappened=false,
isMutable=false,
...
)
getAccountState automatically decodes Borsh-encoded data using the class you provide (which must extend State). You can define your own @StructLayout-annotated types to read custom program accounts.
You can also access raw JSON-parsed account data without defining a typed model, using key-path access:
public static void sample() {
var address = net.deanly.solana.sdk.crypto.PublicKey.valueOf("");
RpcResultObject<List<ResValueTokenAccount>> result = rpc.getRpcHttpApi()
.getTokenAccountsByOwner(
address,
TokenAccountByOwnerFilter.builder()
.programId(SplTokenProgram.PROGRAM_ID)
.build(),
TokenAccountsByOwnerConfig.builder()
.encoding(Encoding.JSON_PARSED)
.build());
for (ResValueTokenAccount value : result.getValue()) {
String mint = (String) value.getAccount().getData().getObjectValue("parsed.info.mint");
String amount = (String) value.getAccount().getData().getObjectValue("parsed.info.tokenAmount.uiAmountString");
System.out.println("Mint: " + mint + ", Amount: " + amount);
}
}This project is inspired by solanaj by Michael Morrell. We appreciate the groundwork laid by its contributors and aim to continue that spirit with an extended and modernized architecture tailored for today’s Java developers.
Licensed under thee MIT License.