Web3j is a powerful and widely-used Java library for interacting with the Ethereum blockchain. It provides developers with the tools necessary to integrate Ethereum's functionality into their applications, from querying account information to executing complex smart contract interactions.
This guide covers the fundamental methods for managing accounts, tokens, and transactions using Web3j, providing clear code examples and practical explanations.
Fetching an Account's Nonce
In Ethereum, a nonce is a critical number associated with an account. It represents the number of confirmed transactions sent from that specific address. The nonce is essential for ensuring the order and uniqueness of transactions, preventing replay attacks.
Every new transaction from an account must have a nonce that is exactly one greater than the nonce of the previous transaction. Here's how to retrieve the current nonce for an address using Web3j.
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");
}
}Retrieving ETH Balance
Checking the Ether balance of an Ethereum address is a fundamental operation. The balance is returned in wei, the smallest denomination of ETH, and is often converted 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;
}
}Querying Token Balances
Interacting with ERC-20 and other token standards requires specific methods to query balances, as these are stored within smart contracts.
Method 1: Direct Smart Contract Call
This method involves creating a function call to the token's smart contract to execute its balanceOf method.
public static BigInteger getTokenBalance(Web3j web3j, String fromAddress, String contractAddress) {
String methodName = "balanceOf";
List<Type> inputParameters = new ArrayList<>();
List<TypeReference<Type>> 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);
EthCall ethCall;
BigInteger balanceValue = BigInteger.ZERO;
try {
ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send();
List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
balanceValue = (BigInteger) results.get(0).getValue();
} catch (IOException e) {
e.printStackTrace();
}
return balanceValue;
}Method 2: Using a Block Explorer API (Mainnet Only)
For simplicity on the Ethereum mainnet, you can use a third-party API like Etherscan. This avoids direct contract interaction but relies on an external service.
String tokenBalanceUrl = "https://api.etherscan.io/api?module=account&action=tokenbalance"
+ "&contractaddress=0x5aA8D6dE8CBf23DAC734E6f904B93bD056B15b81" // Token contract address
+ "&address=0xd4279e30e27f52ca60fac3cc9670c7b9b1eeefdc" // Account address to query
+ "&tag=latest&apikey=YourApiKeyToken"; // Your Etherscan API key
String result = HttpRequestUtil.sendGet(tokenBalanceUrl, "");
TokenBalanceResult tokenBalanceResult = JSON.parseObject(result, TokenBalanceResult.class);
if (tokenBalanceResult.getStatus() == 1) {
BigDecimal tokenCount = new BigDecimal(tokenBalanceResult.getResult())
.divide(new BigDecimal(10).pow(FinalValue.TOKEN_DECIMALS)); // Adjust for token decimals
return tokenCount.floatValue();
}Constructing Transactions
Transactions on Ethereum can be simple value transfers (ETH) or complex smart contract interactions.
Building an ETH Transfer Transaction
Transaction transaction = Transaction.createEtherTransaction(fromAddr, nonce, gasPrice, null, toAddr, value);Building a Smart Contract Function Call
Transaction transaction = Transaction.createFunctionCallTransaction(fromAddr, nonce, gasPrice, null, contractAddr, funcABI);Estimating Gas Limit
Gas limit is the maximum amount of computational work a transaction can consume. Accurately estimating it is crucial to prevent transactions from failing and consuming all provided gas.
public static BigInteger getTransactionGasLimit(Web3j web3j, Transaction transaction) {
try {
EthEstimateGas ethEstimateGas = web3j.ethEstimateGas(transaction).send();
if (ethEstimateGas.hasError()) {
throw new RuntimeException(ethEstimateGas.getError().getMessage());
}
return ethEstimateGas.getAmountUsed();
} catch (IOException e) {
throw new RuntimeException("Network error");
}
}Transferring ETH
Sending Ether requires constructing, signing, and broadcasting a transaction. This process involves checking balances, setting nonces, and estimating gas.
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 balance = Convert.toWei(ethBalance, Convert.Unit.ETHER);
if (balance.compareTo(amount.add(new BigDecimal(gasLimit.toString()))) < 0) {
throw new RuntimeException("Insufficient balance for amount + gas");
}
return signAndSend(web3j, nonce, gasPrice, gasLimit, toAddr, value, data, chainId, privateKey);
}Transferring Tokens
Token transfers involve calling the transfer function on the token's smart contract. The value is sent not to an external address but to the contract itself, with the function call specifying the recipient.
public static String transferToken(Web3j web3j, String fromAddr, String privateKey, String toAddr, String contractAddr, long amount) {
BigInteger nonce = getNonce(web3j, fromAddr);
String method = "transfer";
List<Type> inputArgs = new ArrayList<>();
inputArgs.add(new Address(toAddr));
inputArgs.add(new Uint256(BigDecimal.valueOf(amount).multiply(BigDecimal.TEN.pow(18)).toBigInteger())); // Adjust for decimals
List<TypeReference<Type>> outputArgs = new ArrayList<>();
String funcABI = FunctionEncoder.encode(new Function(method, inputArgs, outputArgs));
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);
}👉 Explore more strategies for efficient token transfers
Signing and Sending a Transaction
The final step for any transaction is to cryptographically sign it with the sender's private key and broadcast it to the network.
public static String signAndSend(Web3j web3j, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, String to, BigInteger value, String data, byte chainId, String privateKey) {
String txHash = "";
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;
if (chainId > ChainId.NONE) {
signMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);
} else {
signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
}
String signData = Numeric.toHexString(signMessage);
if (!"".equals(signData)) {
try {
EthSendTransaction send = web3j.ethSendRawTransaction(signData).send();
txHash = send.getTransactionHash();
} catch (IOException e) {
throw new RuntimeException("Transaction failed");
}
}
return txHash;
}Checking Token Allowance (for ERC-20)
For tokens that have been approved for spending by a decentralized application (DApp) or another address, you can check the approved allowance.
public static BigInteger getAllowanceBalance(Web3j web3j, String fromAddr, String toAddr, String contractAddress) {
String methodName = "allowance";
List<Type> inputParameters = new ArrayList<>();
inputParameters.add(new Address(fromAddr));
inputParameters.add(new Address(toAddr));
List<TypeReference<Type>> outputs = new ArrayList<>();
TypeReference<Uint256> typeReference = new TypeReference<Uint256>() {};
outputs.add(typeReference);
Function function = new Function(methodName, inputParameters, outputs);
String data = FunctionEncoder.encode(function);
Transaction transaction = Transaction.createEthCallTransaction(fromAddr, contractAddress, data);
EthCall ethCall;
BigInteger balanceValue = BigInteger.ZERO;
try {
ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send();
List<Type> result = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
balanceValue = (BigInteger) result.get(0).getValue();
} catch (IOException e) {
e.printStackTrace();
}
return balanceValue;
}Dynamic Gas Price and Limit Management
For more advanced control, especially with smart contracts, you can implement a custom ContractGasProvider. This allows you to specify different gas prices and limits for individual contract functions, optimizing costs and execution.
Greeter greeter = new Greeter(...);
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 NotImplementedException();
}
}
@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 NotImplementedException();
}
}
});Optimizing Transaction Speed with Gas Price
Transaction confirmation time is primarily determined by the gas price you are willing to pay. Miners prioritize transactions with higher fees. Services like ETH Gas Station provide real-time data on recommended gas prices to get your transaction included in the next block quickly.
// Example response from a gas price API
{
"average": 25, // Average gas price (Gwei * 10)
"fastestWait": 0.5,
"fastWait": 0.7,
"fast": 36, // Fast gas price (Gwei * 10)
"safeLowWait": 1.2,
"blockNum": 6274955,
"avgWait": 1.2,
"safeLow": 25 // Safe low gas price (Gwei * 10)
}To use this data, convert the values to wei:
var average_gasprice_wei = average * 10e8;
var fast_gasprice_wei = fast * 10e8;
var safeLow_gasprice_wei = safeLow * 10e8;👉 View real-time tools for gas price estimation
Frequently Asked Questions
What is a nonce in Ethereum and why is it important?
A nonce is a transaction counter for each Ethereum address. It is crucial for maintaining the order of transactions and preventing double-spending or replay attacks. Every new transaction from an address must have a nonce that increments sequentially.
What is the difference between gas price and gas limit?
The gas limit is the maximum amount of computational units you are willing to spend on a transaction. The gas price is the amount of Gwei you are willing to pay per unit of gas. The total transaction fee is calculated as Gas Limit * Gas Price.
Why would my token transfer transaction fail even though I have enough tokens?
The most common reason is insufficient ETH to pay for the gas fee. Executing a token transfer is a smart contract interaction that requires ETH to pay the network fee, even though the token itself is being sent.
How can I speed up a pending transaction?
You can sometimes speed up a transaction by submitting a new transaction with the same nonce but a significantly higher gas price. This signals to miners that you want to replace the previous pending transaction.
What does 'allowance' mean for ERC-20 tokens?
Allowance is a security feature in the ERC-20 standard. It allows a token owner to approve a specific amount of their tokens to be spent by another address (like a decentralized exchange). The approved address can then transfer up to that amount on the owner's behalf.
Should I use a node provider or a service like Etherscan for balance checks?
Using your own node or a dedicated provider (like Infura) via Web3j offers more reliability and privacy for your application. While Etherscan is convenient for quick checks, relying on its API for core functionality can introduce a point of failure and rate limiting.