Offline Crypto Address Validation in Java

Author: Oleksandr Dendeberia

Original post on Foojay: Read More

Backend validation often starts with simple questions.

Is the input empty? Does it have the expected shape? Can it be parsed? Does it belong to the selected domain?

Crypto wallet addresses are no different, except the formats vary across chains and simple regular expressions are rarely enough.

If a Java backend accepts crypto withdrawal addresses, address-book entries, CSV imports, support-tool input, or transaction preflight requests, it should reject clearly invalid addresses before calling RPC nodes, explorers, exchange APIs, or internal money-movement systems.

That first validation layer can be done offline.

This article explains what can be validated locally, what cannot be proven offline, and how to implement a practical Java validation layer using Chainwarden, an open-source library published on Maven Central.

What Can Be Validated Offline?

Offline validation checks properties that are fully contained in the address string itself.

For example:

  • syntax
  • allowed character set
  • encoded length
  • decoded byte length
  • network prefix
  • address version
  • checksum
  • EIP-55 casing for EVM addresses

These checks are deterministic and do not require network access.

That makes them a good fit for:

  • request validation
  • form validation
  • withdrawal preflight checks
  • address-book validation
  • import jobs
  • support tools
  • logging and diagnostics

What Cannot Be Validated Offline?

Offline validation is not a complete safety check.

It cannot prove:

  • account existence
  • balance
  • ownership
  • smart contract status
  • whether a token can be received
  • whether a custodial address requires a memo or destination tag
  • whether an exchange will accept a transfer

For example, XRP has destination-tag requirements in many custodial flows. An offline validator can check whether the XRP address format is valid. It cannot know whether the receiving platform requires a tag for that specific deposit.

That distinction is important in production systems.

Offline validation should be the first gate, not the final decision.

Adding Chainwarden

Add the high-level facade 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")

Validating an Address

The main API is:

AddressValidators.validate(chain, address)

Here is a Bitcoin example:

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());
        }
    }
}

Possible output:

Valid bitcoin address
Format: BECH32

The result contains:

  • valid() – whether validation succeeded
  • chain() – canonical chain id
  • format() – detected address format
  • error() – stable machine-readable error code
  • reason() – human-readable diagnostic text

Returning structured results is useful in production because validation failures can be logged, counted, translated into user-facing messages, or mapped to API error responses.

Validating By Chain Id

Many systems receive the chain as a string from an API request, database field, or configuration file.

Chainwarden also accepts canonical ids and aliases:

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

For example, BNB Smart Chain can be validated with bnb-smart-chain, bsc, or BNB_SMART_CHAIN:

boolean valid = AddressValidators.isValid(
        "bsc",
        "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
);

Mapping Validation Errors

A boolean is enough for quick checks, but applications usually need better feedback.

import org.chainwarden.AddressValidators;
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 keeps the validation layer deterministic while giving API consumers a useful reason when validation fails.

Supported Formats

Chainwarden currently supports:

Chain Formats
Bitcoin Base58Check, Bech32, Bech32m
Ethereum EVM 0x address, EIP-55
BNB Smart Chain EVM 0x address, EIP-55
Base EVM 0x address, EIP-55
Arbitrum One EVM 0x address, EIP-55
Polygon PoS EVM 0x address, EIP-55
Avalanche C-Chain EVM 0x address, EIP-55
TRON Base58Check with TRON prefix
Solana Base58-encoded 32-byte public key
XRP Ledger Classic address, mainnet X-address
TON Raw and user-friendly formats

EVM chains share the same address format: a 0x prefix and 20 bytes encoded as 40 hexadecimal characters. Mixed-case addresses are checked using EIP-55.

Conclusion

Crypto address validation is a good candidate for boring, deterministic infrastructure. The first layer should not need a network call. It should decode the address, check local rules, return structured results, and be clear about its limits.

Chainwarden provides that first layer for Java applications, while deliberately avoiding claims it cannot prove offline.

Project links:

The post Offline Crypto Address Validation in Java appeared first on foojay.