Skip to Content
ExamplesJava Spring

Java Spring Examples

VerifNow provides an official Spring Boot SDK to simplify integration. You can also call the API directly with RestTemplate or WebClient.

Official SDK: 📦 verifnow-spring  — Spring Boot starter for VerifNow 📖 Full example project  — A complete working example


Add the dependency

Declare the starter — verifnow-spring and verifnow-core come with it.

<!-- pom.xml --> <dependency> <groupId>io.verifnow</groupId> <artifactId>verifnow-spring-boot-starter</artifactId> <version>2.7.0</version> </dependency>

Configure your API key

Properties live under the verifnow.api prefix:

# application.properties verifnow.api.apiKey=${VERIFNOW_API_KEY} # verifnow.api.timeoutMs=1500 # default # verifnow.api.failOnError=false # default: accept the value when the API is unreachable

failOnError is false by default, so a value is accepted unverified when the API cannot be reached — the SDK logs a warning naming the rule and the URL. Set it to true where validating matters more than accepting. Upgrade to at least 2.1.1 either way: 2.1.0 and earlier shipped a default base URL with no DNS record, and combined with fail-open they accepted everything while appearing to work.

Validate with annotations

import io.verifnow.core.client.PhoneLineType; import io.verifnow.spring.annotations.VerifNowEmail; import io.verifnow.spring.annotations.VerifNowPhone; import io.verifnow.spring.annotations.VerifNowVat; public record SignupForm( // Accepted when VIES cannot confirm the registration: the number is well formed and its // registration is unknown, not absent. requireRegistered = true rejects that case too. @VerifNowVat String vatNumber, // contact@ and info@ pass unless you opt out with rejectRoleBased = true. @VerifNowEmail(rejectDisposable = true) String email, // Every valid number passes unless its line type is listed. Numbers need a country code. @VerifNowPhone(rejectedLineTypes = { PhoneLineType.PREMIUM_RATE }) String phone ) {}

Each option has its own message, so the user is told which rule rejected the value rather than “invalid”. Full attribute list in the SDK README .

Read the diagnostics

Inject VerifNowClient when a verdict is not enough — storing the VIES source with an invoice, or branching on the line type:

import io.verifnow.core.client.ValidationResult; import io.verifnow.core.client.VatDetails; import io.verifnow.core.client.VerifNowClient; import org.springframework.stereotype.Service; @Service public class BillingSetup { private final VerifNowClient verifnow; public BillingSetup(VerifNowClient verifnow) { this.verifnow = verifnow; } public void record(String vatNumber) { ValidationResult result = verifnow.validate("vat", vatNumber); VatDetails vat = result.getVatDetails(); if (Boolean.FALSE.equals(vat.registered())) { throw new IllegalArgumentException("That VAT number is not registered."); } // null means VIES could not be asked — unknown, never "not registered". boolean confirmed = Boolean.TRUE.equals(vat.registered()); store(vatNumber, confirmed, vat.source(), vat.checkedAt(), vat.consultationNumber()); } }

getPhoneDetails() carries the country, lineType() and the formatted forms; the E.164 form is getNormalizedValue(). For email, getEmailDetails().getSignals() exposes every signal — freeProviderIfComputed() returns empty on FREE and STARTER, where the API does not compute it, so it is never confused with false. These fields need SDK 2.2.0 or later; getIbanDetails() needs 2.3.0 or later, and getNasDetails() 2.4.0 or later.


Option 2: Direct API call with RestTemplate

If you prefer not to use the SDK, you can call the API directly.

Configuration

Add your API key to application.properties:

# application.properties verifnow.api-key=${VERIFNOW_API_KEY} verifnow.base-url=https://api.verifnow.io/api/v1/validate

Model classes

// ValidationResult.java package io.verifnow.client.model; import com.fasterxml.jackson.annotation.JsonProperty; public class ValidationResult { private boolean valid; private String message; @JsonProperty("normalizedValue") private String normalizedValue; @JsonProperty("originalValue") private String originalValue; @JsonProperty("validationLevel") private String validationLevel; @JsonProperty("emailDetails") private EmailDetails emailDetails; public boolean isValid() { return valid; } public String getMessage() { return message; } public String getNormalizedValue() { return normalizedValue; } public String getOriginalValue() { return originalValue; } public String getValidationLevel() { return validationLevel; } public EmailDetails getEmailDetails() { return emailDetails; } }
// EmailDetails.java package io.verifnow.client.model; import com.fasterxml.jackson.annotation.JsonProperty; public class EmailDetails { private EmailSignals signals; @JsonProperty("risk_score") private int riskScore; @JsonProperty("risk_level") private String riskLevel; private String deliverability; @JsonProperty("applied_level") private String appliedLevel; public EmailSignals getSignals() { return signals; } public int getRiskScore() { return riskScore; } public String getRiskLevel() { return riskLevel; } public String getDeliverability() { return deliverability; } }
// EmailSignals.java package io.verifnow.client.model; import com.fasterxml.jackson.annotation.JsonProperty; public class EmailSignals { @JsonProperty("syntax_valid") private boolean syntaxValid; @JsonProperty("mx_valid") private boolean mxValid; @JsonProperty("typo_detected") private boolean typoDetected; @JsonProperty("suggested_domain") private String suggestedDomain; private boolean disposable; @JsonProperty("role_based") private boolean roleBased; @JsonProperty("free_provider") private boolean freeProvider; @JsonProperty("domain_age_days") private Integer domainAgeDays; @JsonProperty("mx_provider") private String mxProvider; @JsonProperty("mx_quality_score") private double mxQualityScore; public boolean isDisposable() { return disposable; } public boolean isTypoDetected() { return typoDetected; } public String getSuggestedDomain() { return suggestedDomain; } public boolean isRoleBased() { return roleBased; } public boolean isFreeProvider() { return freeProvider; } public boolean isSyntaxValid() { return syntaxValid; } public boolean isMxValid() { return mxValid; } }

Service

// VerifNowService.java package io.verifnow.client.service; import io.verifnow.client.model.ValidationResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import java.util.Map; @Service public class VerifNowService { private static final Logger logger = LoggerFactory.getLogger(VerifNowService.class); private final RestTemplate restTemplate; @Value("${verifnow.api-key}") private String apiKey; @Value("${verifnow.base-url}") private String baseUrl; public VerifNowService(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @Retryable( retryFor = { HttpClientErrorException.TooManyRequests.class }, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2) ) public ValidationResult validateEmail(String email) { HttpHeaders headers = new HttpHeaders(); headers.set("X-API-KEY", apiKey); headers.setContentType(MediaType.APPLICATION_JSON); Map<String, String> body = Map.of("value", email); HttpEntity<Map<String, String>> request = new HttpEntity<>(body, headers); try { ResponseEntity<ValidationResult> response = restTemplate.postForEntity( baseUrl + "/email", request, ValidationResult.class ); return response.getBody(); } catch (HttpClientErrorException.Unauthorized e) { throw new RuntimeException("Invalid VerifNow API key. Check VERIFNOW_API_KEY.", e); } catch (HttpClientErrorException.TooManyRequests e) { logger.warn("VerifNow quota exceeded, retrying..."); throw e; // Let @Retryable handle it } catch (HttpClientErrorException e) { logger.error("VerifNow API error: {} {}", e.getStatusCode(), e.getMessage()); throw new RuntimeException("VerifNow validation failed: " + e.getMessage(), e); } } }

RestTemplate configuration

// AppConfig.java package io.verifnow.client.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.annotation.EnableRetry; import org.springframework.web.client.RestTemplate; @Configuration @EnableRetry public class AppConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }

Controller integration

// UserController.java package io.verifnow.client.controller; import io.verifnow.client.model.ValidationResult; import io.verifnow.client.service.VerifNowService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.Map; @RestController @RequestMapping("/api/users") public class UserController { private final VerifNowService verifNowService; public UserController(VerifNowService verifNowService) { this.verifNowService = verifNowService; } @PostMapping("/register") public ResponseEntity<?> register(@RequestBody Map<String, String> payload) { String email = payload.get("email"); ValidationResult validation; try { validation = verifNowService.validateEmail(email); } catch (Exception e) { // Fail open: don't block registration if VerifNow is unreachable return continueRegistration(email); } if (!validation.isValid()) { String suggestion = ""; if (validation.getEmailDetails() != null && validation.getEmailDetails().getSignals() != null && validation.getEmailDetails().getSignals().getSuggestedDomain() != null) { suggestion = validation.getEmailDetails().getSignals().getSuggestedDomain(); } return ResponseEntity.badRequest().body( Map.of( "error", "Please provide a valid, reachable email address.", "suggestion", suggestion ) ); } if (validation.getEmailDetails() != null && validation.getEmailDetails().getSignals() != null && validation.getEmailDetails().getSignals().isDisposable()) { return ResponseEntity.badRequest().body( Map.of("error", "Disposable email addresses are not allowed.") ); } return continueRegistration(email); } private ResponseEntity<?> continueRegistration(String email) { // Your actual user creation logic here return ResponseEntity.ok(Map.of("message", "Registration successful", "email", email)); } }

Reactive WebClient (Spring WebFlux)

For non-blocking, reactive applications:

// VerifNowReactiveService.java package io.verifnow.client.service; import io.verifnow.client.model.ValidationResult; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import java.util.Map; @Service public class VerifNowReactiveService { private final WebClient webClient; public VerifNowReactiveService( @Value("${verifnow.api-key}") String apiKey, @Value("${verifnow.base-url}") String baseUrl ) { this.webClient = WebClient.builder() .baseUrl(baseUrl) .defaultHeader("X-API-KEY", apiKey) .defaultHeader("Content-Type", "application/json") .build(); } public Mono<ValidationResult> validateEmail(String email) { return webClient.post() .uri("/email") .bodyValue(Map.of("value", email)) .retrieve() .onStatus( status -> status.value() == 429, response -> Mono.error(new RuntimeException("Quota exceeded")) ) .bodyToMono(ValidationResult.class); } }
Last updated on