← Back to Blog

BIN Lookup API for Java: Complete Integration Guide

Published: February 5, 2026
Tags: java, tutorial, integration, developer, enterprise

This guide walks you through integrating BINLookupAPI into your Java application, from creating your account to writing production-ready code with proper error handling.

What You'll Build
  • Look up BIN information for any payment card
  • Handle all error cases gracefully
  • Work with both synchronous and asynchronous code
  • Validate cards in a real payment flow

Prerequisites

  • Java 11 or higher (Java 16+ recommended for record classes)
  • Maven or Gradle for dependency management
  • Jackson or Gson for JSON parsing

Add the following dependencies to your project:

Maven (pom.xml):

<dependencies>
    <!-- Jackson for JSON parsing -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.17.0</version>
    </dependency>
</dependencies>

Gradle (build.gradle):

dependencies {
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
}

Step 1: Create Your Account

First, you’ll need a BINLookupAPI account to get your API key.

Account Setup
  1. 1 Go to app.binlookupapi.com/sign-in
  2. 2 Sign in with your Google account
  3. 3 An organisation is created automatically for you

You’ll start on the Development plan which includes 15,000 requests per month with mock responses — perfect for building and testing your integration.

Step 2: Create an API Key

Once logged in:

  1. 1 Navigate to API Keys in the dashboard
  2. 2 Click Create Key
  3. 3 Give it a name (e.g., "Java Development")
  4. 4 Copy the key immediately — it's only shown once

Your API key will look something like: blapi_live_xxxxxxxxxxxxxxxxxxxx

! Security

Store your API key securely. Never commit API keys to version control.

Step 3: Your First BIN Lookup

Let’s start with a simple example to verify everything works:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class SimpleBinLookup {
    private static final String API_KEY = "your_api_key_here";
    private static final String API_URL = "https://api.binlookupapi.com/v1/bin";

    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        String requestBody = "{\"number\": 42467101}";

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(API_URL))
            .header("Authorization", "Bearer " + API_KEY)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(requestBody))
            .build();

        HttpResponse<String> response = client.send(
            request,
            HttpResponse.BodyHandlers.ofString()
        );

        System.out.println(response.body());
    }
}

Expected response:

{
  "data": {
    "bin": "42467101",
    "scheme": "visa",
    "funding": "debit",
    "brand": "VISA",
    "category": "CLASSIC",
    "country": {
      "code": "PL",
      "name": "POLAND"
    },
    "issuer": {
      "name": "ING BANK SLASKI SA",
      "website": null,
      "phone": null
    },
    "currency": "PLN",
    "prepaid": false,
    "commercial": false
  }
}

Step 4: Production-Ready Code with Error Handling

The basic example above doesn’t handle errors properly. Here’s a production-ready implementation:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Optional;

// ============================================================================
// Exception Hierarchy
// ============================================================================

/**
 * Base exception for all BIN lookup errors.
 */
public class BINLookupException extends Exception {
    public BINLookupException(String message) {
        super(message);
    }

    public BINLookupException(String message, Throwable cause) {
        super(message, cause);
    }
}

/**
 * Raised when the BIN format is invalid.
 */
public class InvalidBINException extends BINLookupException {
    public InvalidBINException(String message) {
        super(message);
    }
}

/**
 * Raised when the API key is invalid or missing.
 */
public class AuthenticationException extends BINLookupException {
    public AuthenticationException(String message) {
        super(message);
    }
}

/**
 * Raised when the daily quota has been exceeded.
 */
public class QuotaExceededException extends BINLookupException {
    public QuotaExceededException(String message) {
        super(message);
    }
}

/**
 * Raised when the BIN is not in the database.
 */
public class BINNotFoundException extends BINLookupException {
    public BINNotFoundException(String message) {
        super(message);
    }
}

/**
 * Raised when the API service encounters an error.
 */
public class ServiceException extends BINLookupException {
    public ServiceException(String message) {
        super(message);
    }

    public ServiceException(String message, Throwable cause) {
        super(message, cause);
    }
}

// ============================================================================
// Data Models (Java 16+ Records)
// ============================================================================

/**
 * Card issuer information.
 */
@JsonIgnoreProperties(ignoreUnknown = true)
public record Issuer(
    String name,
    String website,
    String phone
) {}

/**
 * Country information.
 */
@JsonIgnoreProperties(ignoreUnknown = true)
public record Country(
    String code,
    String name
) {}

/**
 * Complete BIN information.
 */
@JsonIgnoreProperties(ignoreUnknown = true)
public record BINInfo(
    String bin,
    String scheme,
    String funding,
    String brand,
    String category,
    Country country,
    Issuer issuer,
    String currency,
    boolean prepaid,
    boolean commercial
) {}

/**
 * API response wrapper.
 */
@JsonIgnoreProperties(ignoreUnknown = true)
record ApiResponse(BINInfo data, String message) {}

// ============================================================================
// BIN Lookup Client
// ============================================================================

/**
 * Client for the BINLookupAPI service.
 */
public class BINLookupClient {

    private static final String BASE_URL = "https://api.binlookupapi.com/v1/bin";
    private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(10);

    private final String apiKey;
    private final HttpClient httpClient;
    private final ObjectMapper objectMapper;

    /**
     * Creates a client using the BINLOOKUP_API_KEY environment variable.
     *
     * @throws IllegalArgumentException if API key is not found
     */
    public BINLookupClient() {
        this(System.getenv("BINLOOKUP_API_KEY"));
    }

    /**
     * Creates a client with the specified API key.
     *
     * @param apiKey Your BINLookupAPI key
     * @throws IllegalArgumentException if API key is null or empty
     */
    public BINLookupClient(String apiKey) {
        if (apiKey == null || apiKey.isBlank()) {
            throw new IllegalArgumentException(
                "API key required. Pass it directly or set " +
                "BINLOOKUP_API_KEY environment variable."
            );
        }

        this.apiKey = apiKey;
        this.httpClient = HttpClient.newBuilder()
            .connectTimeout(DEFAULT_TIMEOUT)
            .build();
        this.objectMapper = new ObjectMapper();
    }

    /**
     * Look up information for a BIN.
     *
     * @param binNumber The BIN to look up (4-8 digits)
     * @return BINInfo object with card details
     * @throws InvalidBINException if the BIN format is invalid
     * @throws AuthenticationException if the API key is invalid
     * @throws QuotaExceededException if the daily quota is exceeded
     * @throws BINNotFoundException if the BIN is not found
     * @throws ServiceException if the API service has an error
     */
    public BINInfo lookup(long binNumber) throws BINLookupException {
        // Validate input
        if (binNumber < 1000 || binNumber > 99999999L) {
            throw new InvalidBINException(
                String.format("BIN must be between 1000 and 99999999, got: %d", binNumber)
            );
        }

        String requestBody = String.format("{\"number\": %d}", binNumber);

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(BASE_URL))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .timeout(DEFAULT_TIMEOUT)
            .POST(HttpRequest.BodyPublishers.ofString(requestBody))
            .build();

        HttpResponse<String> response;
        try {
            response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        } catch (IOException e) {
            throw new ServiceException("Could not connect to API. Check your network.", e);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new ServiceException("Request was interrupted.", e);
        }

        // Handle error responses
        int statusCode = response.statusCode();

        switch (statusCode) {
            case 400 -> {
                String message = extractErrorMessage(response.body(), "Invalid BIN");
                throw new InvalidBINException(message);
            }
            case 401 -> throw new AuthenticationException(
                "Invalid API key. Check your credentials."
            );
            case 402 -> throw new AuthenticationException(
                "No active subscription. Please subscribe to a plan."
            );
            case 403 -> throw new AuthenticationException(
                "API key lacks required permissions."
            );
            case 404 -> throw new BINNotFoundException(
                String.format("BIN %d not found in database.", binNumber)
            );
            case 429 -> throw new QuotaExceededException(
                "Daily quota exceeded. Resets at midnight UTC."
            );
            case 502 -> throw new ServiceException(
                "API gateway error. Please try again later."
            );
        }

        if (statusCode >= 500) {
            throw new ServiceException(
                String.format("API service error (HTTP %d). Please try again later.", statusCode)
            );
        }

        if (statusCode < 200 || statusCode >= 300) {
            throw new BINLookupException(
                String.format("Unexpected error: HTTP %d", statusCode)
            );
        }

        // Parse successful response
        try {
            ApiResponse apiResponse = objectMapper.readValue(response.body(), ApiResponse.class);
            return apiResponse.data();
        } catch (Exception e) {
            throw new ServiceException("Failed to parse API response.", e);
        }
    }

    /**
     * Get quota information from response headers.
     *
     * @return QuotaInfo with current usage details
     */
    public Optional<QuotaInfo> getQuotaInfo() {
        try {
            String requestBody = "{\"number\": 42467101}";

            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(BASE_URL))
                .header("Authorization", "Bearer " + apiKey)
                .header("Content-Type", "application/json")
                .timeout(DEFAULT_TIMEOUT)
                .POST(HttpRequest.BodyPublishers.ofString(requestBody))
                .build();

            HttpResponse<String> response = httpClient.send(
                request,
                HttpResponse.BodyHandlers.ofString()
            );

            int limit = Integer.parseInt(
                response.headers().firstValue("X-Quota-Limit").orElse("0")
            );
            int remaining = Integer.parseInt(
                response.headers().firstValue("X-Quota-Remaining").orElse("0")
            );
            long reset = Long.parseLong(
                response.headers().firstValue("X-Quota-Reset").orElse("0")
            );

            return Optional.of(new QuotaInfo(limit, remaining, reset));
        } catch (Exception e) {
            return Optional.empty();
        }
    }

    private String extractErrorMessage(String responseBody, String defaultMessage) {
        try {
            ApiResponse response = objectMapper.readValue(responseBody, ApiResponse.class);
            return response.message() != null ? response.message() : defaultMessage;
        } catch (Exception e) {
            return defaultMessage;
        }
    }

    /**
     * Quota information.
     */
    public record QuotaInfo(int limit, int remaining, long resetTimestamp) {}
}
i Java Version Compatibility

The code above uses Java 16+ record classes. If you’re using Java 11-15, replace records with regular classes using getters and a constructor. See the alternative implementation in the expanded section below.

Step 5: Using the Client

Here’s how to use the production-ready client:

public class BinLookupExample {
    public static void main(String[] args) {
        // Initialize with API key from environment
        BINLookupClient client = new BINLookupClient();

        // Or pass the key directly
        // BINLookupClient client = new BINLookupClient("your_api_key_here");

        try {
            BINInfo info = client.lookup(42467101);

            System.out.println("Card Network: " + info.scheme());
            System.out.println("Card Type: " + info.funding());
            System.out.println("Issuer: " + info.issuer().name());
            System.out.println("Country: " + info.country().name());
            System.out.println("Is Prepaid: " + info.prepaid());

        } catch (InvalidBINException e) {
            System.err.println("Invalid BIN: " + e.getMessage());

        } catch (AuthenticationException e) {
            System.err.println("Auth error: " + e.getMessage());
            // Check your API key

        } catch (QuotaExceededException e) {
            System.err.println("Quota exceeded: " + e.getMessage());
            // Wait until midnight UTC or upgrade your plan

        } catch (BINNotFoundException e) {
            System.err.println("BIN not found: " + e.getMessage());
            // This BIN isn't in the database

        } catch (ServiceException e) {
            System.err.println("Service error: " + e.getMessage());
            // Retry with exponential backoff

        } catch (BINLookupException e) {
            System.err.println("Unexpected error: " + e.getMessage());
        }
    }
}
* Error Handling Pattern

The exception hierarchy lets you catch specific errors or use the base BINLookupException to handle all API-related errors at once.

Step 6: Async Support with CompletableFuture

For high-throughput applications, here’s an async version using Java’s CompletableFuture:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * Async client for BINLookupAPI.
 */
public class AsyncBINLookupClient implements AutoCloseable {

    private static final String BASE_URL = "https://api.binlookupapi.com/v1/bin";
    private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(10);

    private final String apiKey;
    private final HttpClient httpClient;
    private final ObjectMapper objectMapper;
    private final ExecutorService executor;

    public AsyncBINLookupClient(String apiKey) {
        this.apiKey = apiKey;
        this.executor = Executors.newVirtualThreadPerTaskExecutor(); // Java 21+
        // For Java 11-20, use: Executors.newCachedThreadPool();
        this.httpClient = HttpClient.newBuilder()
            .connectTimeout(DEFAULT_TIMEOUT)
            .executor(executor)
            .build();
        this.objectMapper = new ObjectMapper();
    }

    /**
     * Async BIN lookup.
     */
    public CompletableFuture<BINInfo> lookupAsync(long binNumber) {
        String requestBody = String.format("{\"number\": %d}", binNumber);

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(BASE_URL))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .timeout(DEFAULT_TIMEOUT)
            .POST(HttpRequest.BodyPublishers.ofString(requestBody))
            .build();

        return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(response -> {
                int statusCode = response.statusCode();

                if (statusCode == 401) {
                    throw new RuntimeException(
                        new AuthenticationException("Invalid API key")
                    );
                }
                if (statusCode == 404) {
                    throw new RuntimeException(
                        new BINNotFoundException("BIN " + binNumber + " not found")
                    );
                }
                if (statusCode < 200 || statusCode >= 300) {
                    throw new RuntimeException(
                        new ServiceException("HTTP " + statusCode)
                    );
                }

                try {
                    ApiResponse apiResponse = objectMapper.readValue(
                        response.body(),
                        ApiResponse.class
                    );
                    return apiResponse.data();
                } catch (Exception e) {
                    throw new RuntimeException(
                        new ServiceException("Failed to parse response", e)
                    );
                }
            });
    }

    /**
     * Look up multiple BINs concurrently.
     */
    public CompletableFuture<List<LookupResult>> lookupManyAsync(List<Long> binNumbers) {
        List<CompletableFuture<LookupResult>> futures = binNumbers.stream()
            .map(bin -> lookupAsync(bin)
                .thenApply(info -> new LookupResult(bin, info, null))
                .exceptionally(e -> new LookupResult(bin, null, e.getCause()))
            )
            .collect(Collectors.toList());

        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList())
            );
    }

    @Override
    public void close() {
        executor.shutdown();
    }

    /**
     * Result wrapper for batch lookups.
     */
    public record LookupResult(long bin, BINInfo info, Throwable error) {
        public boolean isSuccess() {
            return info != null && error == null;
        }
    }
}

// Usage
public class AsyncExample {
    public static void main(String[] args) throws Exception {
        try (AsyncBINLookupClient client = new AsyncBINLookupClient("your_api_key")) {

            // Single async lookup
            client.lookupAsync(42467101)
                .thenAccept(info -> System.out.println("Scheme: " + info.scheme()))
                .exceptionally(e -> {
                    System.err.println("Error: " + e.getMessage());
                    return null;
                })
                .join();

            // Multiple lookups in parallel
            List<Long> bins = List.of(42467101L, 51234567L, 37123456L);

            client.lookupManyAsync(bins)
                .thenAccept(results -> {
                    for (var result : results) {
                        if (result.isSuccess()) {
                            System.out.printf("%d: %s%n",
                                result.bin(), result.info().scheme());
                        } else {
                            System.out.printf("%d: Error - %s%n",
                                result.bin(), result.error().getMessage());
                        }
                    }
                })
                .join();
        }
    }
}

Step 7: Real-World Example — Payment Form Validation

Here’s a practical example of using BIN lookup in a payment flow:

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Result of card validation.
 */
public record CardValidationResult(
    boolean valid,
    String cardType,
    String issuer,
    String country,
    boolean isPrepaid,
    List<String> warnings,
    String error
) {
    public static CardValidationResult success(
            String cardType,
            String issuer,
            String country,
            boolean isPrepaid,
            List<String> warnings) {
        return new CardValidationResult(
            true, cardType, issuer, country, isPrepaid, warnings, null
        );
    }

    public static CardValidationResult failure(String error) {
        return new CardValidationResult(
            false, null, null, null, false, null, error
        );
    }

    public static CardValidationResult failure(
            String error,
            String cardType,
            String issuer,
            String country,
            boolean isPrepaid) {
        return new CardValidationResult(
            false, cardType, issuer, country, isPrepaid, null, error
        );
    }
}

/**
 * Card validator using BIN lookup.
 */
public class CardValidator {

    private static final Pattern DIGITS_ONLY = Pattern.compile("\\D");

    private final BINLookupClient client;

    public CardValidator(BINLookupClient client) {
        this.client = client;
    }

    /**
     * Validate a card BIN for payment processing.
     *
     * @param cardNumber The card number (only first 6-8 digits are used)
     * @param blockPrepaid If true, reject prepaid cards
     * @param allowedCountries Set of allowed country codes (e.g., ["US", "GB"])
     * @return CardValidationResult with validation details
     */
    public CardValidationResult validateCardBin(
            String cardNumber,
            boolean blockPrepaid,
            Set<String> allowedCountries) {

        List<String> warnings = new ArrayList<>();

        // Extract digits only
        String digitsOnly = DIGITS_ONLY.matcher(cardNumber).replaceAll("");

        if (digitsOnly.length() < 6) {
            return CardValidationResult.failure(
                "Card number must be at least 6 digits"
            );
        }

        // Use first 8 digits as BIN
        String binStr = digitsOnly.substring(0, Math.min(8, digitsOnly.length()));
        long binNumber = Long.parseLong(binStr);

        BINInfo info;
        try {
            info = client.lookup(binNumber);
        } catch (BINNotFoundException e) {
            return CardValidationResult.failure(
                "Unable to identify card. Please check the number."
            );
        } catch (QuotaExceededException e) {
            // Fail open - allow the transaction but log the issue
            return CardValidationResult.success(
                null, null, null, false,
                List.of("BIN validation skipped due to quota limits")
            );
        } catch (BINLookupException e) {
            // Fail open for service errors
            return CardValidationResult.success(
                null, null, null, false,
                List.of("BIN validation unavailable: " + e.getMessage())
            );
        }

        String cardType = info.scheme();
        String issuerName = info.issuer() != null ? info.issuer().name() : null;
        String countryCode = info.country() != null ? info.country().code() : null;
        String countryName = info.country() != null ? info.country().name() : null;

        // Check prepaid status
        if (blockPrepaid && info.prepaid()) {
            return CardValidationResult.failure(
                "Prepaid cards are not accepted",
                cardType, issuerName, countryCode, true
            );
        }

        // Check country restrictions
        if (allowedCountries != null &&
                !allowedCountries.isEmpty() &&
                countryCode != null &&
                !allowedCountries.contains(countryCode)) {
            return CardValidationResult.failure(
                "Cards from " + countryName + " are not accepted",
                cardType, issuerName, countryCode, info.prepaid()
            );
        }

        // Add warnings for high-risk indicators
        if (info.prepaid()) {
            warnings.add("This is a prepaid card");
        }
        if (info.commercial()) {
            warnings.add("This is a corporate/business card");
        }

        return CardValidationResult.success(
            cardType,
            issuerName,
            countryCode,
            info.prepaid(),
            warnings.isEmpty() ? null : warnings
        );
    }
}

// Usage example
public class PaymentValidationExample {
    public static void main(String[] args) {
        BINLookupClient client = new BINLookupClient();
        CardValidator validator = new CardValidator(client);

        CardValidationResult result = validator.validateCardBin(
            "4246710012345678",
            true,  // block prepaid cards
            Set.of("US", "GB", "CA", "AU")  // allowed countries
        );

        if (result.valid()) {
            System.out.printf("Card accepted: %s from %s%n",
                result.cardType(), result.issuer());

            if (result.warnings() != null) {
                for (String warning : result.warnings()) {
                    System.out.println("Warning: " + warning);
                }
            }
        } else {
            System.out.println("Card rejected: " + result.error());
        }
    }
}
i Fail Open Strategy

For payment validation, consider “failing open” when the API is unavailable. It’s usually better to allow a transaction and verify later than to block all payments during an outage.

Best Practices Summary

Production Checklist
  • Store API key in BINLOOKUP_API_KEY environment variable
  • Handle all error types: network, auth, quota, not-found
  • Set request timeouts (10 seconds recommended)
  • Implement retry logic with exponential backoff for transient errors
  • Consider fail-open strategy for non-critical validation
  • Monitor your quota usage via response headers
  • Use try-with-resources for async client to ensure proper cleanup
  • Consider connection pooling for high-throughput applications

Next Steps

Ready to get started? Create your free account and start building today.