Skip to content

About

Official ZeroBounce Java SDK: email validation, AI scoring, bulk files, email finder and domain search

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

1 watching

Forks

Repository files navigation

ZeroBounce Java SDK

Security

  • Keep API keys on a trusted server. Do not embed them in mobile apps or browser JavaScript that untrusted users can inspect.
  • Custom API base URLs (when supported) must use https://. Do not pass end-user-controlled hosts into those settings.
  • Request URLs include api_key as a query parameter (ZeroBounce API contract). Do not log full request URLs or enable payload debug logging in production.
  • Requests are sent by Apache HttpClient 5, which logs through SLF4J. At DEBUG it logs the request line (with api_key), every request header (including Authorization and API-Key) and the raw request bytes (form fields such as the SMTP password, and uploaded files). The SDK refuses to send while any HttpClient logger that can write the key or server text is at DEBUG or TRACE; the list is under Controlling SDK logging. Keep org.apache.hc at INFO or above in production; that section also shows a configuration. Logback with no configuration file logs everything at DEBUG.

Maven Central

This SDK contains methods for interacting easily with ZeroBounce API. More information about ZeroBounce you can find in the official documentation.
Minimum Java runtime: 21. The SDK is compiled for Java 21 (class file version 65) and will not load on an older JVM.

Installation

You can install ZeroBounceSDK by adding the dependency to your pom.xml file:

<dependency>
    <groupId>com.zerobounce.java</groupId>
    <artifactId>zerobouncesdk</artifactId>
    <version><latest_version></version>
</dependency>

Using the SDK from Maven Central

The SDK is published on Maven Central; the dependency in the Installation section resolves it there. This repository does not commit a built jar. To try an unreleased change from this repository, run ./mvnw install in zero-bounce-sdk/, which installs the SDK into your local ~/.m2 repository under its sdk.version, and depend on that version from your project.

How to deploy the SDK

Releases are published with the GitHub Actions Publish workflow; see Publication below. To publish by hand:

  1. Set autoPublish to false for the central-publishing-maven-plugin in the release profile of the zero-bounce-sdk pom.xml if you don't want the artifact to be published on Maven Central automatically.
  2. Set the GPG passphrase in the MAVEN_GPG_PASSPHRASE environment variable, then run mvn --no-transfer-progress --batch-mode clean deploy -Prelease from the zero-bounce-sdk folder.

Sample app vs. published SDK

The JavaFX sample at the repository root (pom.xml, com.zerobounceexample:zero-bounce-java-sdk-setup-master) depends on com.zerobounce.java:zerobouncesdk from Maven Central with a pinned <version>, not on the zero-bounce-sdk submodule. After you publish a new SDK release, bump that version in the root pom.xml so the demo matches what users install from Central. Dependabot may open a pull request for that bump; if not, update the version manually so we do not forget.

USAGE

Initialize the sdk with your api key:

ZeroBounceSDK.getInstance().initialize("<YOUR_API_KEY>");
ZeroBounceSDK.getInstance().initialize("<YOUR_API_KEY>", timeoutInMillis);
ZeroBounceSDK.getInstance().initialize("<YOUR_API_KEY>", "<YOUR_API_BASE_URL>");

Controlling SDK logging

Two loggers can write during a request:

  • The SDK's own logger stays off until you register a ZBLogger. What it writes has the API key redacted.
  • Apache HttpClient, which sends the requests, logs through your SLF4J backend. At DEBUG it writes the API key, the Warmup key, the SMTP password and uploaded files.

Before each request the SDK checks every HttpClient logger that can write the API key, the SMTP password or text the server sent (17 loggers in all):

  • the request loggers org.apache.hc.client5.http.impl.classic.MainClientExec, org.apache.hc.client5.http.headers, org.apache.hc.client5.http.wire and org.apache.hc.client5.http.impl.routing.DistributedProxySelector, which write the request URI, the request headers and the request bytes;
  • the loggers that write text the server sent, such as a garbled status line or an authentication challenge, which can echo the server's text back: org.apache.hc.client5.http.impl.io.LenientHttpResponseParser; in org.apache.hc.client5.http.impl.auth, BasicScheme, DigestScheme, BearerScheme, NTLMScheme, GGSSchemeBase, ScramScheme, AuthenticationHandler, AuthCacheKeeper and BasicAuthCache; org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy; org.apache.hc.client5.http.impl.classic.ConnectExec; and org.apache.hc.client5.http.protocol.RequestAuthCache.

While any of them is at DEBUG or TRACE, the call sends nothing and reports status 0 with the message "Apache HttpClient debug logging (org.apache.hc) is enabled and would write your API key to the log; set org.apache.hc to INFO or above." A level changed at run time applies to the next call.

SLF4J binds its logging backend on first use. A call made while that is still under way waits for it to finish, for up to 10 seconds. If it has not finished by then, the call sends nothing and reports status 0 with the message "SLF4J is still initialising its logging, so the Apache HttpClient logging level could not be checked and no request was sent; retry the call." Once the backend is bound, the next call is checked as usual. If SLF4J never finishes binding (a binding threw an error), it stays unbound and every later call is refused at once with the same message, instead of each waiting 10 seconds.

Keep org.apache.hc at INFO or above in production. Logback with no configuration file logs everything at DEBUG, so if you use Logback without a logback.xml, start from this one:

<!-- src/main/resources/logback.xml -->
<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- HttpClient writes API keys and passwords at DEBUG. -->
  <logger name="org.apache.hc" level="INFO"/>

  <root level="INFO">
    <appender-ref ref="CONSOLE"/>
  </root>
</configuration>

To send the SDK's own logging to your application's logging framework, register a ZBLogger implementation before issuing any API calls. The helper class ZBLoggers adapts java.util.logging (JUL) out of the box:

import com.zerobounce.ZBLoggers;

ZeroBounceSDK.setLogger(
    ZBLoggers.jul(java.util.logging.Logger.getLogger("ZeroBounceSDK"))
);

// Optional: enable verbose payload logging for troubleshooting only.
ZeroBounceSDK.setLogPayloads(true);

Passing null to ZeroBounceSDK.setLogger(...) resets the logger to a no-op implementation, which disables SDK logging again.

Bulk API v2 types

  • ZBFileStatusResponse – Includes errorReason when present, and filePhase2Status (file_phase_2_status) when the bulk API returns it (for example after optional phase 2 processing).
  • ZBDownloadType – PHASE_1, PHASE_2, or COMBINED for bulk getfile query parameter download_type (validation and scoring).
  • ZBGetFileOptions – Optional downloadType and activityData for validation getFile; scoringGetFile uses downloadType only (activityData is not sent).

Examples

Note: The snippets below print responses for demonstration purposes. Avoid logging raw API data that may contain PII in production systems.

Then you can use any of the SDK methods, for example:

  • Validate an email address
    ZeroBounceSDK.getInstance().validate(
        "<ANY_EMAIL_ADDRESS>",
        "<OPTIONAL_IP_ADDRESS>",
        new ZeroBounceSDK.OnSuccessCallback<ZBValidateResponse>() {
            @Override
            public void onSuccess(ZBValidateResponse response) {
                System.out.println("validate response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("validate error=" + errorResponse.getErrors());
            }
        });
  • Validate batch a list of email addresses
    List<ZBValidateBatchData> emailsData = new ArrayList<ZBValidateBatchData>();
    emailsData.add(new ZBValidateBatchData("valid@example.com", "1.1.1.1"));
    emailsData.add(new ZBValidateBatchData("invalid@example.com", "1.1.1.1"));
    emailsData.add(new ZBValidateBatchData("disposable@example.com", null));
    
    ZeroBounceSDK.getInstance().validateBatch(
        emailsData,
        new ZeroBounceSDK.OnSuccessCallback<ZBValidateBatchResponse>() {
            @Override
            public void onSuccess(ZBValidateBatchResponse response) {
                System.out.println("validateBatch response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("validateBatch error=" + errorResponse.getErrors());
            }
        });
  • Find the email formats based on a given first name and domain

    The first name is optional: pass null to look up the domain's format without one. At least one of the domain and the company name is required.

    ZeroBounceSDK.getInstance().findEmail(
        "<FIRST_NAME_TO_TEST>",
        "<DOMAIN_TO_TEST>",
        null,
        null,
        null,
        new ZeroBounceSDK.OnSuccessCallback<ZBFindEmailResponse>() {
            @Override
            public void onSuccess(ZBFindEmailResponse response) {
                System.out.println("findEmail response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("findEmail error=" + errorResponse.getErrors());
            }
        });
  • Find the email formats based on a given first name and company name
    ZeroBounceSDK.getInstance().findEmail(
        "<FIRST_NAME_TO_TEST>",
        null,
        "<COMPANY_NAME_TO_TEST>",
        null,
        null,
        new ZeroBounceSDK.OnSuccessCallback<ZBFindEmailResponse>() {
            @Override
            public void onSuccess(ZBFindEmailResponse response) {
                System.out.println("findEmail response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("findEmail error=" + errorResponse.getErrors());
            }
        });
  • Find other domain formats based on a given domain
    ZeroBounceSDK.getInstance().findDomain(
        "<DOMAIN_TO_TEST>",
        null,
        new ZeroBounceSDK.OnSuccessCallback<ZBFindDomainResponse>() {
            @Override
            public void onSuccess(ZBFindDomainResponse response) {
                System.out.println("findDomain response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("findDomain error=" + errorResponse.getErrors());
            }
        });
  • Find other domain formats based on a given company name
    ZeroBounceSDK.getInstance().findDomain(
        null,
        "<COMPANY_NAME_TO_TEST>",
        new ZeroBounceSDK.OnSuccessCallback<ZBFindDomainResponse>() {
            @Override
            public void onSuccess(ZBFindDomainResponse response) {
                System.out.println("findDomain response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("findDomain error=" + errorResponse.getErrors());
            }
        });
  • Check how many credits you have left on your account
    ZeroBounceSDK.getInstance().getCredits(
        new ZeroBounceSDK.OnSuccessCallback<ZBCreditsResponse>() {
            @Override
            public void onSuccess(ZBCreditsResponse response) {
                System.out.println("getCredits response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("getCredits error=" + errorResponse.getErrors());
            }
        });
  • Check your API usage for a given period of time
    Date startDate = new Date();    // The start date of when you want to view API usage
    Date endDate = new Date();      // The end date of when you want to view API usage
    
    ZeroBounceSDK.getInstance().getApiUsage(
        startDate, 
        endDate, 
        new ZeroBounceSDK.OnSuccessCallback<ZBGetApiUsageResponse>() {
            @Override
            public void onSuccess(ZBGetApiUsageResponse response) {
                System.out.println("getApiUsage response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("getApiUsage error=" + errorResponse.getErrors());
            }
        });
  • The sendFile API allows user to send a file for bulk email validation
    File myFile = new File("<FILE_PATH>");  // The csv or txt file
    int emailAddressColumn = 3;             // The column index of the email address in the file. Index starts at 1
    
    // sendFile and scoringSendFile throw the checked ZBException for an invalid argument,
    // such as an email column below 1, before anything is sent.
    try {
        ZeroBounceSDK.getInstance().sendFile(
            myFile,
            emailAddressColumn,
            null,
            new ZeroBounceSDK.OnSuccessCallback<ZBSendFileResponse>() {
                @Override
                public void onSuccess(ZBSendFileResponse response) {
                    System.out.println("sendFile response=" + response.toString());
                }
            }, new ZeroBounceSDK.OnErrorCallback() {
                @Override
                public void onError(ErrorResponse errorResponse) {
                    System.out.println("sendFile error=" + errorResponse.getErrors());
                }
            });
    } catch (ZBException e) {
        System.out.println("sendFile rejected its arguments: " + e.getMessage());
    }

    Bulk validation uses https://bulkapi.zerobounce.net/v2. See v2 send file, v2 file status, and v2 get file.

    Optional multipart field allow_phase_2 (validation bulk only): use ZeroBounceSDK.SendFileOptions and setAllowPhase2(Boolean):

    ZeroBounceSDK.SendFileOptions opts = new ZeroBounceSDK.SendFileOptions()
            .setAllowPhase2(true);
    
    try {
        ZeroBounceSDK.getInstance().sendFile(
            myFile,
            emailAddressColumn,
            opts,
            successCallback,
            errorCallback);
    } catch (ZBException e) {
        System.out.println("sendFile rejected its arguments: " + e.getMessage());
    }
  • The getFile API allows users to get the validation results file for the file been submitted using sendFile API
    String fileId = "<FILE_ID>";                    // The returned file ID when calling sendfile API
    String downloadPath = "<FILE_DOWNLOAD_PATH>";   // The path where the file will be downloaded
    
    ZeroBounceSDK.getInstance().getFile(
        fileId,
        downloadPath,
        new ZeroBounceSDK.OnSuccessCallback<ZBGetFileResponse>() {
            @Override
            public void onSuccess(ZBGetFileResponse response) {
                System.out.println("getfile response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("getfile error=" + errorResponse.getErrors());
            }
        });

    Optional query parameters (validation getFile only for activity data; scoringGetFile does not send activity_data):

    import com.zerobounce.ZBDownloadType;
    import com.zerobounce.ZBGetFileOptions;
    
    ZBGetFileOptions getOpts = new ZBGetFileOptions()
            .setDownloadType(ZBDownloadType.COMBINED)
            .setActivityData(true);
    
    ZeroBounceSDK.getInstance().getFile(
        fileId,
        downloadPath,
        getOpts,
        successCallback,
        errorCallback);
    
    ZeroBounceSDK.getInstance().scoringGetFile(
        fileId,
        downloadPath,
        new ZBGetFileOptions().setDownloadType(ZBDownloadType.PHASE_2),
        successCallback,
        errorCallback);

    phase_2 / combined downloads apply when phase 2 was enabled for the file and file_phase_2_status is Complete. JSON error bodies may still use HTTP 200; the SDK surfaces them via the failure callback.

  • Check the status of a file uploaded via sendFile API
    String fileId = "<FILE_ID>";    // The returned file ID when calling sendfile API
    
    ZeroBounceSDK.getInstance().fileStatus(
        fileId,
        new ZeroBounceSDK.OnSuccessCallback<ZBFileStatusResponse>() {
            @Override
            public void onSuccess(ZBFileStatusResponse response) {
                System.out.println("fileStatus response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("fileStatus error=" + errorResponse.getErrors());
            }
        });
  • Delete the file that was submitted using sendFile API. File can be deleted only when its status is Complete
    String fileId = "<FILE_ID>";    // The returned file ID when calling sendfile API
    
    ZeroBounceSDK.getInstance().deleteFile(
        fileId,
        new ZeroBounceSDK.OnSuccessCallback<ZBDeleteFileResponse>() {
            @Override
            public void onSuccess(ZBDeleteFileResponse response) {
                System.out.println("deleteFile response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("deleteFile error=" + errorResponse.getErrors());
            }
        });
  • Gather insights into your subscribers’ overall email engagement. The request returns data regarding opens, clicks, forwards and unsubscribes that have taken place in the past 30, 90, 180 or 365 days.
    ZeroBounceSDK.getInstance().getActivityData(
        "<ANY_EMAIL_ADDRESS>",
        new ZeroBounceSDK.OnSuccessCallback<ZBActivityDataResponse>() {
            @Override
            public void onSuccess(ZBActivityDataResponse response) {
                System.out.println("getActivityData response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("getActivityData error=" + errorResponse.getErrors());
            }
        });

AI Scoring API

  • The scoringSendFile API allows user to send a file for bulk email validation
    File myFile = new File("<FILE_PATH>");  // The csv or txt file
    int emailAddressColumn = 3;             // The column index of the email address in the file. Index starts at 1
    
    // sendFile and scoringSendFile throw the checked ZBException for an invalid argument,
    // such as an email column below 1, before anything is sent.
    try {
        ZeroBounceSDK.getInstance().scoringSendFile(
            myFile,
            emailAddressColumn,
            null,
            new ZeroBounceSDK.OnSuccessCallback<ZBSendFileResponse>() {
                @Override
                public void onSuccess(ZBSendFileResponse response) {
                    System.out.println("scoringSendFile response=" + response.toString());
                }
            }, new ZeroBounceSDK.OnErrorCallback() {
                @Override
                public void onError(ErrorResponse errorResponse) {
                    System.out.println("scoringSendFile error=" + errorResponse.getErrors());
                }
            });
    } catch (ZBException e) {
        System.out.println("scoringSendFile rejected its arguments: " + e.getMessage());
    }
  • The scoringGetFile API allows users to get the validation results file for the file been submitted using scoringSendFile API
    String fileId = "<FILE_ID>";                    // The returned file ID when calling scoringSendfile API
    String downloadPath = "<FILE_DOWNLOAD_PATH>";   // The path where the file will be downloaded
    
    ZeroBounceSDK.getInstance().scoringGetFile(
        fileId,
        downloadPath,
        new ZeroBounceSDK.OnSuccessCallback<ZBGetFileResponse>() {
            @Override
            public void onSuccess(ZBGetFileResponse response) {
                System.out.println("scoringGetfile response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("scoringGetfile error=" + errorResponse.getErrors());
            }
        });
  • Check the status of a file uploaded via scoringSendFile API
    String fileId = "<FILE_ID>";    // The returned file ID when calling scoringSendfile API
    
    ZeroBounceSDK.getInstance().scoringFileStatus(
        fileId,
        new ZeroBounceSDK.OnSuccessCallback<ZBFileStatusResponse>() {
            @Override
            public void onSuccess(ZBFileStatusResponse response) {
                System.out.println("scoringFileStatus response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("scoringFileStatus error=" + errorResponse.getErrors());
            }
        });
  • Deletes the file that was submitted using scoring scoringSendFile API. File can be deleted only when its status is Complete
    String fileId = "<FILE_ID>";    // The returned file ID when calling scoringSendfile API
    
    ZeroBounceSDK.getInstance().scoringDeleteFile(
        fileId,
        new ZeroBounceSDK.OnSuccessCallback<ZBDeleteFileResponse>() {
            @Override
            public void onSuccess(ZBDeleteFileResponse response) {
                System.out.println("scoringDeleteFile response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("scoringDeleteFile error=" + errorResponse.getErrors());
            }
        });

Validate options

validate has an overload that takes ZBValidateOptions. An option left unset is not sent, and booleans go on the wire as the strings "true"/"false".

ZBValidateOptions options = new ZBValidateOptions()
        .setTimeout(30)                 // 3-60 seconds
        .setActivityData(true)          // fills getActiveInDays() / getActiveFirstSeen()
        .setVerifyPlus(true)
        .setDomainInfo(true)            // fills getDomainWebsiteExists() / getDomainRegistrantCompanyName()
        .setGreylistProcessing(true)
        .setCallbackUrl("https://example.com/greylist")
        .setUsePost(true);              // form-encoded POST /v2/validate instead of GET

ZeroBounceSDK.getInstance().validate("<EMAIL>", null, options,
        response -> System.out.println(response),
        error -> System.out.println(error.getErrors()));

setCallbackUrl is where the greylist retry posts its result, so it is rejected without setGreylistProcessing(true) — before any request is sent. While that retry is still running the API answers with a Message instead of a result, which lands on response.getMessage().

Real-time AI scoring

ZeroBounceSDK.getInstance().scoreEmail("<EMAIL>", onSuccess, onError);        // GET /v2/scoring
ZeroBounceSDK.getInstance().scoreEmail("<EMAIL>", true, onSuccess, onError);  // form-encoded POST

Filters

ZeroBounceSDK.getInstance().addFilter("block", "domain", "example.com", onSuccess, onError);
ZeroBounceSDK.getInstance().listFilters(false, onSuccess, onError); // true sends a POST
ZeroBounceSDK.getInstance().deleteFilter("block", "domain", "example.com", onSuccess, onError);

S3 and SFTP/FTP bulk

Queue a job on a file that already sits in your Amazon S3 bucket, or on your SFTP/FTP server. See the S3 and SFTP docs. These live on the bulkapi host without the /v2 path.

ZeroBounceSDK zb = ZeroBounceSDK.getInstance();

// Amazon S3. The file name is the full path, as returned by listS3Files.
zb.listS3Files(null, null, onSuccess, onError);   // optional bucket, search

// file_name and return_url are required on every one of these jobs.
ZBBulkFileJobOptions s3 = new ZBBulkFileJobOptions()
        .setFileName("lists/leads.csv")
        .setHasHeaderRow(true)
        .setReturnUrl("https://example.com/zerobounce-done");

zb.validateS3File(s3, onSuccess, onError);
zb.scoreS3File(s3, onSuccess, onError);
zb.emailFinderS3File(s3, onSuccess, onError);
zb.domainSearchS3File(s3, onSuccess, onError);
zb.exportS3File("<FILE_ID>", "my-export-bucket", onSuccess, onError);

// validateS3File and scoreS3File also take an options object with the column
// indexes those two jobs accept. They send the same required fields, so
// file_name and return_url are required on these overloads as well.
zb.validateS3File(new ZeroBounceSDK.ValidateS3FileOptions()
        .setFileName("lists/leads.csv")
        .setReturnUrl("https://example.com/zerobounce-done")
        .setEmailAddressColumn(1)
        .setAllowPhase2(true), onSuccess, onError);

zb.scoreS3File(new ZeroBounceSDK.ScoreS3FileOptions()
        .setFileName("lists/leads.csv")
        .setReturnUrl("https://example.com/zerobounce-done")
        .setEmailAddressColumn(1), onSuccess, onError);

// SFTP/FTP. The server, path and protocol come from your ZeroBounce settings.
// The docs list file_name as optional for validateSFTPFile, but the live API
// requires it there too.
ZBBulkFileJobOptions sftp = new ZBBulkFileJobOptions()
        .setFileName("my_file.txt")
        .setReturnUrl("https://example.com/zerobounce-done");

zb.validateSFTPFile(sftp, onSuccess, onError);
zb.scoreSFTPFile(sftp, onSuccess, onError);
zb.emailFinderSFTPFile(sftp, onSuccess, onError);
zb.domainSearchSFTPFile(sftp, onSuccess, onError);

// Note the field: this one takes export_path, not export_bucket.
// Pass null to use the export path from your settings.
zb.exportSFTPFile("<FILE_ID>", "/exports", onSuccess, onError);

List evaluator

The list evaluator has its own base URL, not a path built from the bulk one, and the file id is URL-encoded as a single path segment. The upload and the result read that same base URL, so overriding it moves both.

// Upload a list to evaluate. The email column is 1-based.
ZeroBounceSDK.getInstance().sendListEvaluatorFile(new File("list.csv"), 1, onSuccess, onError);

ZeroBounceSDK.getInstance().getListEvaluatorResult("<FILE_ID>", onSuccess, onError);

// Submit a finished evaluation for validation. This takes the evaluator's
// file_id as a form field, not a file upload.
ZeroBounceSDK.getInstance().sendEvaluatedFile("<FILE_ID>", onSuccess, onError);

Inbox Placement Tester

Runs on its own host, authenticates with an Authorization: Bearer header, and sends camelCase JSON bodies. It spends the account's inbox-test credits.

ZeroBounceSDK zb = ZeroBounceSDK.getInstance();

zb.getInboxPlacementProviders(onSuccess, onError);
zb.getInboxPlacementTestTypes(onSuccess, onError);

// The name is at most 64 characters and testType is 1, 2 or 3. A type 1 test
// mails from your own address, so senderEmail is required with it. All three
// are rejected before the request is sent.
zb.createInboxPlacementTest("Launch",
        new ZeroBounceSDK.CreateInboxPlacementTestOptions()
                .setTestType(1)
                .setSenderEmail("news@example.com")
                .setWebhookUrl("https://example.com/ipt-webhook"),
        onSuccess, onError);

// Send your campaign to the seeds you got back, then read the results.
zb.getInboxPlacementTest("<TEST_ID>", onSuccess, onError);

// The page size goes on the wire as page_size; as pageSize the API ignores it
// and answers with the whole list.
zb.listInboxPlacementTests(
        new ZeroBounceSDK.ListInboxPlacementTestsOptions()
                .setStatus("finished")
                .setPage(1)
                .setPageSize(25)
                .setCreatedAfter("2026-01-01")
                .setCreatedBefore("2026-02-01"),
        onSuccess, onError);
zb.setInboxPlacementWebhook("https://example.com/ipt-webhook", onSuccess, onError);
zb.getInboxPlacementWebhook(onSuccess, onError);
zb.deleteInboxPlacementTest("<TEST_ID>", onSuccess, onError);

Per-API base URLs

The list evaluator and the Inbox Placement Tester are separate hosts, so overriding the main base URL alone leaves them pointing at the live API.

ZeroBounceSDK.getInstance().initialize(
        "<API_KEY>",
        "https://api.zerobounce.net/v2",
        "https://bulkapi.zerobounce.net/v2/listevaluator",
        "https://bulkapi.zerobounce.net/api/ipt");

Breaking Changes:

guessFormat has been deprecated. To continue using your existing code, you must migrate to findEmail or findDomain . The change is not a simple one-to-one replacement, as the functionality has been split:

  • If you were finding a person's email format, use the new findEmail() method.
  • If you were only determining the domain's general email pattern, use the new findDomain() method.

Migration Example:

  • Old (Deprecated)

    ZeroBounceSDK.getInstance().guessFormat(
        "<DOMAIN_TO_TEST>",
        null,
        null,
        null,
        new ZeroBounceSDK.OnSuccessCallback<ZBEmailFinderResponse>() {
            @Override
            public void onSuccess(ZBEmailFinderResponse response) {
                System.out.println("guessFormat response=" + response.toString());
            }
        }, new ZeroBounceSDK.OnErrorCallback() {
            @Override
            public void onError(ErrorResponse errorResponse) {
                System.out.println("guessFormat error=" + errorResponse.getErrors());
            }
        });
  • New Methods

    • Find the email formats based on a given first name and domain
      ZeroBounceSDK.getInstance().findEmail(
          "<FIRST_NAME_TO_TEST>",
          "<DOMAIN_TO_TEST>",
          null,
          null,
          null,
          new ZeroBounceSDK.OnSuccessCallback<ZBFindEmailResponse>() {
              @Override
              public void onSuccess(ZBFindEmailResponse response) {
                  System.out.println("findEmail response=" + response.toString());
              }
          }, new ZeroBounceSDK.OnErrorCallback() {
              @Override
              public void onError(ErrorResponse errorResponse) {
                  System.out.println("findEmail error=" + errorResponse.getErrors());
              }
          });
    • Find the email formats based on a given first name and company name
      ZeroBounceSDK.getInstance().findEmail(
          "<FIRST_NAME_TO_TEST>",
          null,
          "<COMPANY_NAME_TO_TEST>",
          null,
          null,
          new ZeroBounceSDK.OnSuccessCallback<ZBFindEmailResponse>() {
              @Override
              public void onSuccess(ZBFindEmailResponse response) {
                  System.out.println("findEmail response=" + response.toString());
              }
          }, new ZeroBounceSDK.OnErrorCallback() {
              @Override
              public void onError(ErrorResponse errorResponse) {
                  System.out.println("findEmail error=" + errorResponse.getErrors());
              }
          });
    • Find other domain formats based on a given domain
      ZeroBounceSDK.getInstance().findDomain(
          "<DOMAIN_TO_TEST>",
          null,
          new ZeroBounceSDK.OnSuccessCallback<ZBFindDomainResponse>() {
              @Override
              public void onSuccess(ZBFindDomainResponse response) {
                  System.out.println("findDomain response=" + response.toString());
              }
          }, new ZeroBounceSDK.OnErrorCallback() {
              @Override
              public void onError(ErrorResponse errorResponse) {
                  System.out.println("findDomain error=" + errorResponse.getErrors());
              }
          });
    • Find other domain formats based on a given company name
      ZeroBounceSDK.getInstance().findDomain(
          null,
          "<COMPANY_NAME_TO_TEST>",
          new ZeroBounceSDK.OnSuccessCallback<ZBFindDomainResponse>() {
              @Override
              public void onSuccess(ZBFindDomainResponse response) {
                  System.out.println("findDomain response=" + response.toString());
              }
          }, new ZeroBounceSDK.OnErrorCallback() {
              @Override
              public void onError(ErrorResponse errorResponse) {
                  System.out.println("findDomain error=" + errorResponse.getErrors());
              }
          });

New Features and Enhancements

Custom API URL Support

The ZeroBounceSDK.initialize() method can now accepts an apiBaseUrl parameter. This allows you to specify a custom base URL for the ZeroBounce API.

Migration / Usage

The existing way of initializing the SDK is still valid.

  • Default Usage (No Change Required). If you don't provide a URL, the SDK will continue to use the standard ZeroBounce API endpoint:
ZeroBounceSDK.getInstance().initialize("<YOUR_API_KEY>");
ZeroBounceSDK.getInstance().initialize("<YOUR_API_KEY>", timeoutInMillis);
  • Initialize the SDK with your API key and URL:
ZeroBounceSDK.getInstance().initialize("<YOUR_API_KEY>", "<YOUR_API_BASE_URL>");

The SDK now exposes a set of predefined constants for different geographical API endpoints, allowing for more precise network routing.

Available API Endpoints

You can specify a custom API base URL during initialization by using the new optional apiBaseUrl parameter in initialize(). For convenience, the following constants are available in the ZBConstants class:

Constant URL Value Description
API_DEFAULT_URL https://api.zerobounce.net/v2/ The global default endpoint.
API_USA_URL https://api-us.zerobounce.net/v2/ The US-specific endpoint for lower latency in the Americas.
API_EU_URL https://api-eu.zerobounce.net/v2/ The EU-specific endpoint for compliance and lower latency in Europe.

Usage Example:

To use the EU endpoint for initialization:

ZeroBounceSDK.getInstance().initialize("<YOUR_API_KEY>", ZBConstants.API_EU_URL);

A base URL is used without its trailing slash, so the constants above and a URL such as https://api-eu.zerobounce.net/v2 send requests to the same .../v2/validate path.

Testing

Run tests with Docker

From the sdk-docs/ folder in the SDKs monorepo:

cd sdk-docs
docker compose build java
docker compose run --rm java

docker compose build java already runs the test suite (the image's RUN mvn -q test); docker compose run --rm java runs it again offline, as the image's unprivileged user (uid 65532), from the Maven repository the build filled.

You can also build from this repository root: docker build -t zerobounce-java-sdk:test . (uses .dockerignore to exclude target/, IDE files, and local-libs).

Run tests (local)

From the zero-bounce-sdk module directory:

cd zero-bounce-sdk
mvn test

Or with the Maven wrapper: ./mvnw test.

Documentation

You can generate the documentation using your desired IDE or using's maven's javadoc command.

Publication

Publishing to Maven Central goes through the Sonatype Central Portal, using the GitHub Actions Publish workflow (.github/workflows/publish.yml). It is triggered manually per tag; it does not run on a push or on release creation.

  1. Set sdk.version in zero-bounce-sdk/pom.xml to the new version, create a git tag for it (e.g. v2.2.0), and push the tag.
  2. In the repo go to Actions → Publish, click Run workflow, enter the tag (e.g. v2.2.0), and run.
  3. The workflow checks that the tag exists, then runs mvn --no-transfer-progress --batch-mode clean deploy -Prelease in zero-bounce-sdk, with the signing passphrase in MAVEN_GPG_PASSPHRASE, which the Maven GPG plugin reads. That builds and tests the SDK, signs the jars with GPG, and the central-publishing-maven-plugin uploads them to the Central Portal. autoPublish is true in the release profile, so the deployment is published once the Portal has validated it, without a manual step.
  4. When the publish job succeeds, the workflow creates a GitHub Release for the tag with generated release notes.

The workflow reads these secrets: CENTRAL_TOKEN_USERNAME and CENTRAL_TOKEN_PASSWORD (a Central Portal user token, for the central server), SIGNING_KEY (the GPG private key) and SIGNING_PASSWORD (its passphrase). If any of them changes, update it on GitHub.

Local setup for manual release

To publish from your local machine instead of the workflow:

  1. Import the GPG key to your local machine (see below).
  2. Add a <server> with the id central to your ~/.m2/settings.xml, with a Central Portal user token as its username and password.
  3. If you want to check the deployment before it goes to Maven Central, set autoPublish to false for the central-publishing-maven-plugin in the release profile of zero-bounce-sdk/pom.xml.
  4. From the zero-bounce-sdk folder, with MAVEN_GPG_PASSPHRASE set to the passphrase, run:
    mvn --no-transfer-progress --batch-mode clean deploy -Prelease

With autoPublish set to false, the upload waits on the Central Portal deployments page until you publish (or drop) it there. Once published, the artifact appears on the Maven Repository and on Central search; the MVN Repository index picks it up later.

Exporting and importing PGP keys

  1. Export the keys:

    gpg --list-keys  # In order to obtain the key hash for the next step
    gpg --export -a <LAST_8_DIGITS> > public.key
    gpg --export-secret-key -a <LAST_8_DIGITS> > private key
  2. Import the keys:

    gpg --import public.key
    gpg --import private.key
  3. Check that the new keys are imported:

    gpg --list-keys
    gpg --list-secret-keys

Warmup API

The Warmup API is a separate product. It has its own host, its own team-scoped key, and its own client; your validation key will not work with it.

ZeroBounceWarmup warmup = ZeroBounceWarmup.getInstance();  // or new ZeroBounceWarmup(), one per key
warmup.initialize("<YOUR_WARMUP_API_KEY>");

warmup.listSendingAccounts(onSuccess, onError);
warmup.getProgressToday(onSuccess, onError);
warmup.downloadSeeds(csv -> { /* CSV text, columns seed,provider */ }, onError);

Add a from-address you run yourself, with one of the five standard engagement rules (0 Default, 1 Slow Pace, 2 Repair, 3 Boost, 4 Protect):

warmup.addSendingAccount("sender@example.com", "self_managed",
        new ZeroBounceWarmup.AddSendingAccountOptions().setEngagementRuleId(0),
        onSuccess, onError);

A custom engagement rule can be changed only once every seven days; inside that window the API answers 429.

warmup.createCustomEngagement("sender@example.com", 20, 20, 35, onSuccess, onError);  // reply, important, click %

checkDomainDmarc takes an email address, not a domain — the live API rejects a bare domain even though the docs describe one:

warmup.checkDomainDmarc("sender@example.com", null, onSuccess, onError);

Every documented empty success (204, or an empty 200) arrives as a null payload. A payload that is not a JSON object — /seeds is an array, /seed-count a bare number — comes back wrapped under data. Failures carry the HTTP status on ErrorResponse.getStatusCode(). Nothing is stored by testSmtpConfiguration, and the credentials you pass it are masked out of any error it raises.

Live API suite

mvn test never touches the network: the live suite lives outside the Maven module's source root, so CI does not even compile it. It needs real keys, which come from the environment and are never written anywhere:

mvn -f zero-bounce-sdk/pom.xml dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt
mvn -f zero-bounce-sdk/pom.xml compile
CP="zero-bounce-sdk/target/classes:$(cat /tmp/cp.txt)"
javac -cp "$CP" -d live/out live/LiveApi.java

export ZEROBOUNCE_API_KEY=...          # validation API
export ZEROBOUNCE_WARMUP_API_KEY=...   # Warmup API
java -cp "$CP:live/out" com.zerobounce.live.LiveApi

By default it only makes free calls: sandbox addresses, reads, and requests that are expected to fail but still prove the API accepts the payload. It checks that the credit balance did not move. Two flags opt in to more:

Flag What it adds
ZEROBOUNCE_LIVE_SPEND=1 billable calls (scoring, activity, email finder) and a bulk round trip that deletes its own job
ZEROBOUNCE_LIVE_CHANGES=1 reversible account changes — a test filter and a self-managed Warmup sending account — each undone in cleanup even when a test fails

It never creates an Inbox Placement test, sets the Inbox Placement webhook, or starts ZB-managed Warmup onboarding, because the API cannot undo any of those.

Run it by hand before every release: the offline suite proves the SDK sends what we think it sends, and only the live suite proves the API accepts it.

Publish

  1. Bump sdk.version in zero-bounce-sdk/pom.xml, commit, tag (vX.Y.Z), push tag.
  2. Actions → Publish → Run workflow with that tag.

Registry: com.zerobounce.java:zerobouncesdk on Maven Central

License

MIT. See LICENSE.

About

Official ZeroBounce Java SDK: email validation, AI scoring, bulk files, email finder and domain search

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages