Developing decentralized applications on Ethereum using Java requires a solid understanding of the web3j library—a lightweight, reactive, type-safe RPC client for integrating with Ethereum blockchain nodes. Whether you're building wallets, DeFi tools, or NFT marketplaces, mastering common operations like balance checks, transaction signing, and gas estimation is crucial.
This guide compiles practical web3j code snippets that every developer should have in their toolkit. All examples are optimized for clarity, performance, and real-world usage while maintaining compatibility with the latest Ethereum standards.
Retrieving Account Nonce
The nonce represents the number of transactions sent from a given Ethereum address. It's essential for preventing replay attacks and ensuring transaction order.
public static BigInteger getNonce(Web3j web3j, String addr) {
try {
EthGetTransactionCount getNonce = web3j.ethGetTransactionCount(addr, DefaultBlockParameterName.PENDING).send();
if (getNonce == null) {
throw new RuntimeException("Network error");
}
return getNonce.getTransactionCount();
} catch (IOException e) {
throw new RuntimeException("Network error");
}
}👉 Discover how to securely manage Ethereum transactions using web3j
Checking ETH Balance
To retrieve the ETH balance of an account, use eth_getBalance. The result is returned in Wei, so convert it to Ether for readability.
public static BigDecimal getBalance(Web3j web3j, String address) {
try {
EthGetBalance ethGetBalance = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send();
return Convert.fromWei(new BigDecimal(ethGetBalance.getBalance()), Convert.Unit.ETHER);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}This method ensures accurate balance display in user-facing applications.
Getting ERC-20 Token Balance (Method 1)
For retrieving token balances directly via smart contract calls, this approach uses ABI encoding to invoke the balanceOf function.
public static BigInteger getTokenBalance(Web3j web3j, String fromAddress, String contractAddress) {
String methodName = "balanceOf";
List<Type> inputParameters = new ArrayList<>();
List<TypeReference<?>> outputParameters = new ArrayList<>();
Address address = new Address(fromAddress);
inputParameters.add(address);
TypeReference<Uint256> typeReference = new TypeReference<Uint256>() {};
outputParameters.add(typeReference);
Function function = new Function(methodName, inputParameters, outputParameters);
String data = FunctionEncoder.encode(function);
Transaction transaction = Transaction.createEthCallTransaction(fromAddress, contractAddress, data);
try {
EthCall ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send();
List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
return (BigInteger) results.get(0).getValue();
} catch (IOException e) {
e.printStackTrace();
return BigInteger.ZERO;
}
}This method works across any EVM-compatible network and doesn't rely on third-party APIs.
Getting Token Balance via Etherscan API (Method 2)
Alternatively, you can query token balances using Etherscan’s public API—ideal for frontend or lightweight services.
String tokenBalanceUrl = "https://api.etherscan.io/api?module=account&action=tokenbalance"
+ "&contractaddress=0x5aA8D6dE8CBf23DAC734E6f904B93bD056B15b81"
+ "&address=0xd4279e30e27f52ca60fac3cc9670c7b9b1eeefdc"
+ "&tag=latest&apikey=YourApiKeyToken";
String result = HttpRequestUtil.sendGet(tokenBalanceUrl, "");
TokenBalanceResult tokenBalanceResult = JSON.parseObject(result, TokenBalanceResult.class);
if (tokenBalanceResult.getStatus() == 1) {
BigDecimal tokenCount = new BigDecimal(tokenBalanceResult.getResult())
.divide(BigDecimal.TEN.pow(FinalValue.TOKEN_DECIMALS));
return tokenCount.floatValue();
}⚠️ Note: This method only works on Ethereum mainnet and requires an API key.
Constructing Transactions
Use these templates to build different types of transactions:
// Sending ETH
Transaction ethTx = Transaction.createEtherTransaction(fromAddr, nonce, gasPrice, null, toAddr, value);
// Calling a smart contract function
Transaction contractTx = Transaction.createFunctionCallTransaction(fromAddr, nonce, gasPrice, null, contractAddr, funcABI);These foundational patterns are used across wallet integrations and dApp backends.
Estimating Gas Limit
Accurate gas estimation prevents out-of-gas failures and improves user experience.
public static BigInteger getTransactionGasLimit(Web3j web3j, Transaction transaction) {
try {
EthEstimateGas estimateGas = web3j.ethEstimateGas(transaction).send();
if (estimateGas.hasError()) {
throw new RuntimeException(estimateGas.getError().getMessage());
}
return estimateGas.getAmountUsed();
} catch (IOException e) {
throw new RuntimeException("Network error");
}
}Always estimate gas before signing any transaction.
Transferring ETH
A complete workflow for sending ETH with balance validation and error handling.
public static String transferETH(Web3j web3j, String fromAddr, String privateKey, String toAddr, BigDecimal amount, String data) {
BigInteger nonce = getNonce(web3j, fromAddr);
BigInteger value = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger();
Transaction transaction = Transaction.createEtherTransaction(fromAddr, nonce, gasPrice, null, toAddr, value);
BigInteger gasLimit = getTransactionGasLimit(web3j, transaction);
BigDecimal ethBalance = getBalance(web3j, fromAddr);
BigDecimal totalCost = amount.add(new BigDecimal(gasLimit.toString()));
if (Convert.toWei(ethBalance, Convert.Unit.ETHER).compareTo(totalCost.toBigInteger()) < 0) {
throw new RuntimeException("Insufficient balance");
}
return signAndSend(web3j, nonce, gasPrice, gasLimit, toAddr, value, data, chainId, privateKey);
}👉 Learn how to optimize gas costs in your Ethereum transactions
Transferring ERC-20 Tokens
Transfer tokens safely by checking both ETH (for gas) and token balances.
public static String transferToken(Web3j web3j, String fromAddr, String privateKey, String toAddr, String contractAddr, long amount) {
BigInteger nonce = getNonce(web3j, fromAddr);
List<Type> inputArgs = new ArrayList<>();
inputArgs.add(new Address(toAddr));
inputArgs.add(new Uint256(BigDecimal.valueOf(amount).multiply(BigDecimal.TEN.pow(18)).toBigInteger()));
List<TypeReference<?>> outputArgs = new ArrayList<>();
Function function = new Function("transfer", inputArgs, outputArgs);
String funcABI = FunctionEncoder.encode(function);
Transaction transaction = Transaction.createFunctionCallTransaction(fromAddr, nonce, gasPrice, null, contractAddr, funcABI);
BigInteger gasLimit = getTransactionGasLimit(web3j, transaction);
BigDecimal ethBalance = getBalance(web3j, fromAddr);
BigInteger tokenBalance = getTokenBalance(web3j, fromAddr, contractAddr);
if (Convert.toWei(ethBalance, Convert.Unit.ETHER).toBigInteger().compareTo(gasLimit) < 0) {
throw new RuntimeException("Insufficient ETH for gas");
}
if (tokenBalance.compareTo(BigDecimal.valueOf(amount).toBigInteger()) < 0) {
throw new RuntimeException("Insufficient token balance");
}
return signAndSend(web3j, nonce, gasPrice, gasLimit, contractAddr, BigInteger.ZERO, funcABI, chainId, privateKey);
}Signing and Sending Raw Transactions
Securely sign transactions offline using private keys.
public static String signAndSend(Web3j web3j, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, String to, BigInteger value, String data, byte chainId, String privateKey) {
RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, value, data);
if (privateKey.startsWith("0x")) privateKey = privateKey.substring(2);
ECKeyPair ecKeyPair = ECKeyPair.create(new BigInteger(privateKey, 16));
Credentials credentials = Credentials.create(ecKeyPair);
byte[] signMessage = (chainId > ChainId.NONE)
? TransactionEncoder.signMessage(rawTransaction, chainId, credentials)
: TransactionEncoder.signMessage(rawTransaction, credentials);
String hexValue = Numeric.toHexString(signMessage);
try {
EthSendTransaction response = web3j.ethSendRawTransaction(hexValue).send();
return response.getTransactionHash();
} catch (IOException e) {
throw new RuntimeException("Transaction failed");
}
}👉 Build secure blockchain applications with advanced transaction controls
Checking Token Allowance
Before transferring tokens on behalf of another user (e.g., in DeFi), verify their approved allowance.
public static BigInteger getAllowanceBalance(Web3j web3j, String ownerAddr, String spenderAddr, String contractAddress) {
Function function = new Function(
"allowance",
Arrays.asList(new Address(ownerAddr), new Address(spenderAddr)),
Arrays.asList(new TypeReference<Uint256>() {})
);
String data = FunctionEncoder.encode(function);
Transaction transaction = Transaction.createEthCallTransaction(ownerAddr, contractAddress, data);
try {
EthCall response = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send();
List<Type> result = FunctionReturnDecoder.decode(response.getValue(), function.getOutputParameters());
return (BigInteger) result.get(0).getValue();
} catch (IOException e) {
e.printStackTrace();
return BigInteger.ZERO;
}
}Dynamic Gas Pricing for Smart Contracts
Improve efficiency by setting different gas prices per function call using a custom ContractGasProvider.
greeter.setGasProvider(new DefaultGasProvider() {
@Override
public BigInteger getGasPrice(String contractFunc) {
switch (contractFunc) {
case Greeter.FUNC_GREET: return BigInteger.valueOf(22_000_000_000L);
case Greeter.FUNC_KILL: return BigInteger.valueOf(44_000_000_000L);
default: throw new UnsupportedOperationException();
}
}
@Override
public BigInteger getGasLimit(String contractFunc) {
switch (contractFunc) {
case Greeter.FUNC_GREET: return BigInteger.valueOf(4_300_000);
case Greeter.FUNC_KILL: return BigInteger.valueOf(5_300_000);
default: throw new UnsupportedOperationException();
}
}
});This technique optimizes cost and speed based on function criticality.
Speeding Up Transaction Confirmation with Gas Price
Use real-time gas data from ethgasstation.info to prioritize transaction inclusion:
{
"average": 25,
"fast": 36,
"safeLow": 25
}Multiply values by 1e8 to convert from Gwei*10 to Wei:
- Fast:
fast * 1e8→ higher fee → faster confirmation
Monitor network congestion dynamically for optimal UX.
Frequently Asked Questions
Q: Can I use web3j for BNB Chain or other EVM chains?
A: Yes! Web3j supports all EVM-compatible networks. Just change the node URL and chain ID accordingly.
Q: Is it safe to handle private keys in Java code?
A: Only in secure environments. For production apps, consider hardware wallets or secure key vaults.
Q: How do I handle failed transactions?
A: Always check eth_sendRawTransaction response. Use block explorers to verify status via hash.
Q: What is the difference between PENDING and LATEST block parameters?
A: PENDING includes unconfirmed transactions; LATEST refers to the most recent mined block.
Q: Why does my transaction run out of gas even after estimation?
A: Complex contract logic may vary per state. Add a small buffer (e.g., +10%) to estimated gas limit.
Q: Can I batch multiple calls in web3j?
A: Yes. Use BatchRequest to reduce RPC overhead when fetching multiple balances or states.
Keywords: Ethereum development, web3j Java library, blockchain programming, smart contract interaction, transaction signing, gas optimization, ERC-20 token balance