Essential Web3j Code Snippets for Ethereum Development

·

Web3j is a powerful, highly modular library for interacting with the Ethereum blockchain from Java and Android applications. This guide compiles essential code snippets to streamline your development process, providing practical examples for common tasks such as querying balances, transferring assets, and managing transactions.

Retrieving an Account's Nonce

The nonce is a critical value representing the number of transactions sent from a specific Ethereum address. It prevents transaction replay attacks and ensures each transaction is unique.

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 occurred");
        }
        return getNonce.getTransactionCount();
    } catch (IOException e) {
        throw new RuntimeException("Network connection error");
    }
}

Querying ETH Balance

Retrieve the Ether balance for any Ethereum address. The balance is returned in ETH units after conversion from wei.

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

Method 1: Direct Contract Interaction

This method directly queries the smart contract using the balanceOf function, compatible with any ERC-20 token.

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 Etherscan API (Mainnet Only)

For mainnet applications, you can leverage external APIs like Etherscan to retrieve token balances, avoiding direct contract calls.

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";
String result = HttpRequestUtil.sendGet(tokenBalanceUrl, "");
TokenBalanceResult tokenBalanceResult = JSON.parseObject(result, TokenBalanceResult.class);
System.out.println(tokenBalanceResult.toString());
if (tokenBalanceResult.getStatus() == 1) {
    BigDecimal tokenCount = new BigDecimal(tokenBalanceResult.getResult())
            .divide(new BigDecimal(10).pow(FinalValue.TOKEN_DECIMALS));
    return tokenCount.floatValue();
}

Constructing Transactions

Creating ETH Transfer Transactions

Transaction transaction = Transaction.createEtherTransaction(fromAddr, nonce, gasPrice, null, toAddr, value);

Creating Smart Contract Function Call Transactions

Transaction transaction = Transaction.createFunctionCallTransaction(fromAddr, nonce, gasPrice, null, contractAddr, funcABI);

Estimating Gas Limit

Accurately estimating gas limits prevents transaction failures and optimizes costs.

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 occurred");
    }
}

Transferring ETH

Execute Ether transfers with proper balance validation and gas estimation.

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 transfer and gas fees");
    }
    return signAndSend(web3j, nonce, gasPrice, gasLimit, toAddr, value, data, chainId, privateKey);
}

Transferring Tokens

Method 1: Standard Token Transfer

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()));
    List<TypeReference<?>> outputArgs = new ArrayList<>();
    String funcABI = FunctionEncoder.encode(new Function(method, inputArgs, outputArgs));
    Transaction transaction = Transaction.createFunctionCallTransaction(fromAddr, nonce, gasPrice, null, contractAddr, funcABI);
    RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, null, contractAddr, null, funcABI);
    BigInteger gasLimit = getTransactionGasLimit(web3j, transaction);
    BigDecimal ethBalance = getBalance(web3j, fromAddr);
    BigInteger tokenBalance = getTokenBalance(web3j, fromAddr, contractAddr);
    BigInteger balance = Convert.toWei(ethBalance, Convert.Unit.ETHER).toBigInteger();
    if (balance.compareTo(gasLimit) < 0) {
        throw new RuntimeException("Insufficient ETH for gas fees");
    }
    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);
}

Method 2: Alternative Token Transfer Approach

public static String transferToken2(String fromAddr, String toAddr, String amount) {
    String contractAddress = "0xa22c2217e785f7796c9e8826c6be55c2e481f9f5";
    Web3j web3j = MyWalletUtils.getWeb3j();
    Credentials credentials = MyWalletUtils.getCredentials();
    System.out.println("My wallet address: " + credentials.getAddress());
    try {
        EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(
                fromAddr, DefaultBlockParameterName.LATEST).sendAsync().get();
        BigInteger nonce = ethGetTransactionCount.getTransactionCount();
        Function function = new Function(
                "transfer",
                Arrays.asList(new Address(toAddr), new Uint256(new BigInteger(amount))),
                Arrays.asList(new TypeReference<Bool>() {}));
        String encodedFunction = FunctionEncoder.encode(function);
        RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, Convert.toWei("18", Convert.Unit.GWEI).toBigInteger(),
                Convert.toWei("100000", Convert.Unit.WEI).toBigInteger(), contractAddress, encodedFunction);
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
        String hexValue = Numeric.toHexString(signedMessage);
        System.out.println("Transfer hexValue: " + hexValue);
        EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).sendAsync().get();
        if (ethSendTransaction.hasError()) {
            System.out.println("Transfer error: " + ethSendTransaction.getError().getMessage());
            return ethSendTransaction.getError().getMessage();
        } else {
            String transactionHash = ethSendTransaction.getTransactionHash();
            return "";
        }
    } catch (Exception e) {
        e.printStackTrace();
        return e.getMessage();
    }
}

Signing and Sending Transactions

Securely sign transactions before broadcasting them 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();
            System.out.println(JSON.toJSONString(send));
        } catch (IOException e) {
            throw new RuntimeException("Transaction processing error");
        }
    }
    return txHash;
}

Checking Allowance for Delegate Spending

Query the amount of tokens a spender is authorized to spend on behalf of the token owner.

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<?>> 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 Pricing and Limits

Customize gas parameters for different smart contract functions by implementing a custom ContractGasProvider.

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 Confirmation Speed

To accelerate transaction confirmation, adjust gas prices based on network conditions. Services like EthGasStation provide real-time gas price recommendations.

{
    "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,
    "block_time": 13.876288659793815,
    "speed": 0.9481897143119544,
    "fastest": 330,
    "safeLow": 25         // Safe low gas price (Gwei * 10)
}

Convert these values to wei for transaction submission:

BigInteger averageGasPriceWei = average.multiply(BigInteger.valueOf(100000000));
BigInteger fastGasPriceWei = fast.multiply(BigInteger.valueOf(100000000));
BigInteger safeLowGasPriceWei = safeLow.multiply(BigInteger.valueOf(100000000));

👉 Explore advanced Ethereum development strategies

Frequently Asked Questions

What is Web3j and why is it useful for Ethereum development?
Web3j is a lightweight Java library for working with Ethereum blockchain that allows developers to interact with Ethereum networks without writing custom integration code. It provides comprehensive functionality for creating wallets, deploying smart contracts, managing transactions, and querying blockchain data, making it ideal for enterprise applications and mobile development.

How do I handle different gas requirements for various smart contract functions?
You can implement a custom ContractGasProvider that specifies different gas prices and limits for each function in your smart contract. This approach allows you to optimize costs and performance based on the complexity of each function call, ensuring sufficient gas allocation while avoiding overpayment.

What are the best practices for error handling in Web3j transactions?
Always implement comprehensive error handling that checks for network issues, insufficient balances, transaction reverts, and gas estimation problems. Use try-catch blocks around network calls, validate responses for errors, and provide meaningful error messages to users. Additionally, implement retry logic for transient network issues.

How can I optimize transaction costs when using Web3j?
Monitor current gas prices using services like EthGasStation and adjust your gas price accordingly based on urgency requirements. Implement proper gas estimation to avoid overpaying, batch transactions when possible, and consider using layer-2 solutions for high-frequency operations to significantly reduce costs.

What security considerations should I keep in mind when working with private keys?
Never hardcode private keys in your source code or version control systems. Use secure credential storage solutions, implement proper key management practices, and consider using hardware security modules for production applications. Always validate addresses and transaction details before signing, and implement appropriate access controls.

How do I test my Web3j integration before deploying to mainnet?
Utilize test networks like Goerli or Sepolia for development and testing. Implement comprehensive unit tests using Web3j's mock support, and consider using mainnet forking tools for more realistic testing environments. Always test with small amounts first and implement proper monitoring and alerting for transactions.