api.
Real-time VerificationFrameworks

Spring Boot

Getting Started

Spring Boot's robust ecosystem and dependency injection make integrating user.cleaning clean and maintainable. We'll create a service that uses Spring's RestTemplate to communicate with our API.

First, store your API key securely in your application configuration:

application.yml
user-cleaning:
  api-key: ${USER_CLEANING_API_KEY}

If you don't have an API key yet, check out our Quickstart guide to generate one.

IP Forwarding for Rate Limiting

For user.cleaning's rate limiting to work correctly, you need to forward the real client IP with each request. Add this helper method to extract the client IP:

util/IpUtils.java
import jakarta.servlet.http.HttpServletRequest;

public class IpUtils {
    public static String getClientIp(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
            return xForwardedFor.split(",")[0].trim();
        }
        String xRealIp = request.getHeader("X-Real-IP");
        if (xRealIp != null && !xRealIp.isEmpty()) {
            return xRealIp;
        }
        return request.getRemoteAddr();
    }
}

Production: If you're behind nginx, Cloudflare, or a load balancer, X-Forwarded-For is set automatically.

Development/testing: Without a reverse proxy forwarding X-Forwarded-For, the integration will see your server's IP instead of actual client IPs. This means all requests appear to come from the same IP, and after 5 disposable email attempts, all registrations will be blocked until the IP is unbanned.

If you experience unexpected registration blocks, verify your reverse proxy is forwarding client IPs correctly. You can unban blocked IPs at API Settings and configure number of registration attempts before block or disable it completely at Color Configuration.

Creating the Validation Service

Let's create a Spring service that handles communication with the user.cleaning API:

service/EmailValidationService.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.http.*;
import java.util.Map;

@Service
public class EmailValidationService {
    
    @Value("${user-cleaning.api-key}")
    private String apiKey;
    
    public Map<String, Object> checkEmail(String email, String clientIp) {
        try {
            RestTemplate restTemplate = new RestTemplate();
            
            HttpHeaders headers = new HttpHeaders();
            headers.set("X-API-Key", apiKey);
            headers.set("X-Forwarded-For", clientIp);
            headers.set("X-Real-IP", clientIp);
            
            String url = "https://api.user.cleaning/v1/external-api-requests/check-email?email=" + email;
            
            ResponseEntity<Map> response = restTemplate.exchange(
                url, HttpMethod.POST, new HttpEntity<>(headers), Map.class
            );
            
            return response.getBody();
        } catch (HttpClientErrorException.Forbidden e) {
            return Map.of(
                "category", "banned",
                "message", "Access denied"
            );
        } catch (Exception e) {
            return Map.of("category", "white"); // Fail open
        }
    }
}

The API responds with a JSON object containing a category field that tells you how to handle the email address.

Integration Approaches

Validating in Controllers

The most straightforward approach is to inject the validation service into your controller and throw a ResponseStatusException when you encounter a disposable email:

controller/RegistrationController.java
import org.springframework.web.server.ResponseStatusException;
import org.springframework.http.HttpStatus;
import jakarta.servlet.http.HttpServletRequest;
import static com.example.util.IpUtils.getClientIp;

@RestController
public class RegistrationController {
    
    @Autowired
    private EmailValidationService emailService;
    
    @PostMapping("/register")
    public ResponseEntity<?> register(
            @RequestBody RegistrationRequest request,
            HttpServletRequest httpRequest) {
        
        String clientIp = getClientIp(httpRequest);
        Map<String, Object> result = emailService.checkEmail(request.getEmail(), clientIp);
        String category = (String) result.get("category");
        
        if ("banned".equals(category)) {
            throw new ResponseStatusException(
                HttpStatus.FORBIDDEN,
                (String) result.get("message")
            );
        }
        
        if ("black".equals(category)) {
            throw new ResponseStatusException(
                HttpStatus.BAD_REQUEST,
                "Disposable emails are not allowed"
            );
        }
        
        if ("grey".equals(category)) {
            // Suspicious email — require phone verification
            User user = userService.create(request, true);
            return ResponseEntity.status(HttpStatus.CREATED)
                .body(Map.of(
                    "message", "Please enter your phone number for verification",
                    "userId", user.getId()
                ));
        }
        
        // 'white' — all good
        User user = userService.create(request, false);
        return ResponseEntity.status(HttpStatus.CREATED)
            .body(Map.of("userId", user.getId()));
    }
}

This approach works well when you have just a few endpoints that need email validation and want direct control over the logic.

Creating a Custom Validator Annotation

Spring's validation framework allows you to create custom annotations that integrate seamlessly with Bean Validation. This makes your validation logic reusable and declarative.

First, create the annotation:

validation/NotDisposableEmail.java
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = NotDisposableEmailValidator.class)
public @interface NotDisposableEmail {
    String message() default "Disposable emails are not allowed";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Then implement the validator logic:

validation/NotDisposableEmailValidator.java
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;

public class NotDisposableEmailValidator implements ConstraintValidator<NotDisposableEmail, String> {
    
    @Autowired
    private EmailValidationService emailService;
    
    @Override
    public boolean isValid(String email, ConstraintValidatorContext context) {
        if (email == null || email.isEmpty()) {
            return true; 
        }
        
        // Note: IP forwarding not available in Bean Validation validators
        Map<String, Object> result = emailService.checkEmail(email, "unknown");
        String category = (String) result.get("category");
        return !"black".equals(category) && !"banned".equals(category);
    }
}

Now you can use this annotation in any DTO:

dto/RegistrationRequest.java
public class RegistrationRequest {
    
    @Email
    @NotDisposableEmail
    private String email;
    
    @Size(min = 8)
    private String password;
    
    // getters/setters
}

Spring will automatically validate emails when the request is processed. Note that Bean Validation validators don't have access to HttpServletRequest, so IP forwarding isn't available with this approach. If you need IP-based rate limiting, use the controller approach instead.

Understanding the Results

The category field can have three possible values:

  • black — This is a disposable or temporary email address that should be blocked
  • grey — Suspicious patterns were detected (like alias chains or plus addressing), consider requiring phone verification
  • white — The email looks legitimate and can be accepted without restrictions

For a complete description of the output data structure and all available fields, see our Categories Reference.

On this page