Crypto address validation is one of those small backend features that becomes
important very quickly.
If you are building a wallet, exchange flow, payment form, fintech dashboard, or
internal operations tool, you usually want to reject clearly invalid addresses
before anything touches an RPC node, explorer API, queue, or withdrawal system.
This tutorial shows how to validate crypto wallet addresses offline in Java
using Chainwarden.
Chainwarden is an open-source Java library for local crypto address validation.
It validates address syntax, encoding, prefixes, decoded lengths, and checksums
without calling remote nodes, explorers, RPC endpoints, or exchange APIs.
What offline validation can check
Offline validation can catch many common mistakes:
- wrong address length
- invalid Base58/Base64/hex characters
- wrong network prefix
- invalid checksum
- invalid EIP-55 checksum casing
- unsupported address format for a chain
- leading or trailing whitespace
It is fast, deterministic, and works without network access.
What offline validation cannot check
Offline validation does not prove that an address is safe to use.
It cannot verify:
- account existence
- account balance
- address ownership
- smart contract status
- whether an exchange requires a memo or destination tag
- whether a token can be received at that destination
Think of offline validation as the first gate: it answers "does this look like a
valid address for this chain?", not "is this a correct withdrawal destination?"
Install
Add Chainwarden from Maven Central:
<dependency>
<groupId>org.chainwarden</groupId>
<artifactId>chainwarden-core</artifactId>
<version>0.1.0</version>
</dependency>
For Gradle:
implementation("org.chainwarden:chainwarden-core:0.1.0")
Validate one address
The main entry point is AddressValidators.validate(chain, address).
import org.chainwarden.AddressValidators;
import org.chainwarden.Chain;
import org.chainwarden.common.validation.AddressValidationResult;
public class ValidateBitcoinAddress {
public static void main(String[] args) {
AddressValidationResult result = AddressValidators.validate(
Chain.BITCOIN,
"bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"
);
if (result.valid()) {
System.out.println("Valid " + result.chain() + " address");
System.out.println("Format: " + result.format());
} else {
System.out.println("Invalid address");
System.out.println("Error: " + result.error());
System.out.println("Reason: " + result.reason());
}
}
}
Example output:
Valid bitcoin address
Format: BECH32
The result contains:
-
valid()- whether validation succeeded -
chain()- canonical chain id, such asbitcoin -
format()- detected format, such asBECH32,EIP55, orBASE58CHECK -
error()- stable machine-readable error code -
reason()- human-readable diagnostic text
Validate by chain id
If the chain comes from a user request, config file, database row, or API
payload, you can use a string chain id:
import org.chainwarden.AddressValidators;
import org.chainwarden.common.validation.AddressValidationResult;
public class ValidateByChainId {
public static void main(String[] args) {
AddressValidationResult result = AddressValidators.validate(
"ethereum",
"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
);
System.out.println(result.valid());
System.out.println(result.chain());
System.out.println(result.format());
}
}
Output:
true
ethereum
EIP55
Aliases are also supported. For example, BNB Smart Chain can be validated with
bnb-smart-chain, bsc, or BNB_SMART_CHAIN.
boolean valid = AddressValidators.isValid(
"bsc",
"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
);
Handle invalid addresses
For production code, avoid returning only true or false. The structured
result is more useful for logging, analytics, API responses, and support tools.
import org.chainwarden.AddressValidators;
import org.chainwarden.common.validation.AddressValidationError;
import org.chainwarden.common.validation.AddressValidationResult;
public class ValidationMessage {
public static String messageFor(String chain, String address) {
AddressValidationResult result = AddressValidators.validate(chain, address);
if (result.valid()) {
return "Address is valid";
}
return switch (result.error()) {
case UNSUPPORTED_CHAIN -> "This chain is not supported yet";
case EMPTY -> "Address is required";
case SURROUNDING_WHITESPACE -> "Remove leading or trailing spaces";
case INVALID_CHECKSUM -> "Address checksum is invalid";
case INVALID_PREFIX -> "Address prefix does not match the selected chain";
case INVALID_LENGTH -> "Address length is invalid";
case INVALID_CHARACTER -> "Address contains invalid characters";
case INVALID_WORKCHAIN -> "TON workchain is not supported";
case INVALID_ENCODING -> "Address encoding is invalid";
case INVALID_FORMAT -> "Address format is invalid";
case NONE -> "Address is valid";
};
}
}
This makes the validation layer friendlier without losing machine-readable error
codes.
Validate several chains
Here is a compact example validating several different address formats:
import org.chainwarden.AddressValidators;
import org.chainwarden.common.validation.AddressValidationResult;
import java.util.Map;
public class MultiChainValidation {
public static void main(String[] args) {
Map<String, String> addresses = Map.of(
"bitcoin", "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8",
"ethereum", "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"tron", "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC",
"solana", "11111111111111111111111111111111",
"ton", "0:0000000000000000000000000000000000000000000000000000000000000000"
);
addresses.forEach((chain, address) -> {
AddressValidationResult result = AddressValidators.validate(chain, address);
System.out.printf(
"%s -> valid=%s, format=%s, error=%s%n",
chain,
result.valid(),
result.format(),
result.error()
);
});
}
}
Supported chains
At the time of writing, Chainwarden supports:
| Chain | Chain id | Formats |
|---|---|---|
| Bitcoin | bitcoin |
Base58Check, Bech32, Bech32m |
| Ethereum | ethereum |
EVM 0x, EIP-55 |
| BNB Smart Chain | bnb-smart-chain |
EVM 0x, EIP-55 |
| Base | base |
EVM 0x, EIP-55 |
| Arbitrum One | arbitrum-one |
EVM 0x, EIP-55 |
| Polygon PoS | polygon-pos |
EVM 0x, EIP-55 |
| Avalanche C-Chain | avalanche-c-chain |
EVM 0x, EIP-55 |
| TRON | tron |
Base58Check with TRON prefix |
| Solana | solana |
Base58-encoded 32-byte public key |
| XRP Ledger | xrp |
Classic address, mainnet X-address |
| TON | ton |
Raw and user-friendly formats |
Example API response
If you expose validation through your own backend, a response can stay very
simple:
{
"valid": true,
"chain": "bitcoin",
"format": "BECH32",
"error": "NONE",
"reason": "valid"
}
For invalid input:
{
"valid": false,
"chain": "ethereum",
"format": "UNKNOWN",
"error": "INVALID_CHECKSUM",
"reason": "invalid EIP-55 checksum"
}
This shape is useful because clients can display a human message while still
using error as a stable programmatic value.
When to use offline validation
Offline validation is a good fit for:
- signup and onboarding forms
- withdrawal address forms
- address books
- transaction preflight checks
- admin dashboards
- support tooling
- import jobs
- backend API validation
It should be combined with chain-specific business rules when money movement is
involved.
For example, XRP may require a destination tag depending on the exchange or
custodian. Offline address validation can tell you whether the XRP address shape
is valid. It cannot tell you whether a destination tag is required by the
recipient.
Final thoughts
Address validation should be boring infrastructure. It should be fast,
deterministic, testable, and explicit about what it can and cannot prove.
Chainwarden tries to keep that boundary clear:
- validate local address properties
- avoid network calls
- return structured results
- keep chain-specific behavior isolated
- expose a small Java facade for application code
Project links:
- Website and demo: https://chainwarden.org
- Maven Central: https://central.sonatype.com/artifact/org.chainwarden/chainwarden-core
- API docs: https://api.chainwarden.org/q/swagger-ui/
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.