DEV Community

realNameHidden
realNameHidden

Posted on

How We Reduced Payment API Latency by 60% Using ExecutorService in Spring Boot

🏦 Business Scenario (Very Common in FinTech)

Imagine a payment processing service.

Before processing a payment, the system must validate:

  1. Account Status (active / blocked)
  2. Balance Check
  3. Fraud Risk Check

Each validation:

  • Calls a different internal service
  • Takes 300–800 ms
  • Is independent

❌ Bad approach (sequential):

  • Total time β‰ˆ 2 seconds
  • High latency β†’ SLA breach

βœ… Good approach (parallel using ExecutorService):

  • All checks run in parallel
  • Total time β‰ˆ max(800 ms)

🧠 Why ExecutorService Here?

  • Controlled thread pool (avoid thread explosion)
  • Parallel execution
  • Better SLA
  • Clean error handling

This is exactly where ExecutorService shines.


Architecture Flow

Client
  |
  v
Payment API
  |
  +-- Account Validation (Thread-1)
  +-- Balance Check     (Thread-2)
  +-- Fraud Check       (Thread-3)
  |
  v
Final Decision
Enter fullscreen mode Exit fullscreen mode

1️⃣ ExecutorService Configuration

package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Configuration
public class ExecutorConfig {

    @Bean
    public ExecutorService executorService() {
        // Controlled pool for validation tasks
        return Executors.newFixedThreadPool(3);
    }
}
Enter fullscreen mode Exit fullscreen mode

2️⃣ Validation Service (Parallel Tasks)

package com.example.service;

import org.springframework.stereotype.Service;

import java.util.concurrent.Callable;

@Service
public class ValidationTasks {

    public Callable<Boolean> accountCheck() {
        return () -> {
            Thread.sleep(500); // simulate service call
            return true; // account is active
        };
    }

    public Callable<Boolean> balanceCheck() {
        return () -> {
            Thread.sleep(700); // simulate service call
            return true; // sufficient balance
        };
    }

    public Callable<Boolean> fraudCheck() {
        return () -> {
            Thread.sleep(800); // simulate service call
            return true; // low risk
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

3️⃣ Payment Orchestrator Service

This is where ExecutorService is actually used.

package com.example.service;

import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

@Service
public class PaymentValidationService {

    private final ExecutorService executorService;
    private final ValidationTasks tasks;

    public PaymentValidationService(
            ExecutorService executorService,
            ValidationTasks tasks) {
        this.executorService = executorService;
        this.tasks = tasks;
    }

    public boolean validatePayment() throws Exception {

        List<Future<Boolean>> results = executorService.invokeAll(
                List.of(
                        tasks.accountCheck(),
                        tasks.balanceCheck(),
                        tasks.fraudCheck()
                )
        );

        // If any validation fails β†’ reject payment
        for (Future<Boolean> result : results) {
            if (!result.get()) {
                return false;
            }
        }
        return true;
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ” Why invokeAll()?

  • Submits multiple tasks together
  • Waits until all complete
  • Clean & readable for orchestration logic

4️⃣ REST Controller

package com.example.controller;

import com.example.service.PaymentValidationService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PaymentController {

    private final PaymentValidationService service;

    public PaymentController(PaymentValidationService service) {
        this.service = service;
    }

    @PostMapping("/validate-payment")
    public String validatePayment() throws Exception {

        boolean valid = service.validatePayment();

        return valid
                ? "Payment validation successful"
                : "Payment validation failed";
    }
}
Enter fullscreen mode Exit fullscreen mode

5️⃣ curl Request

curl -X POST http://localhost:8080/validate-payment
Enter fullscreen mode Exit fullscreen mode

6️⃣ Response

Payment validation successful
Enter fullscreen mode Exit fullscreen mode

⏱ Performance Comparison

Approach Approx Time
Sequential ~2.0 sec
ExecutorService (parallel) ~0.8 sec

➑ 60%+ latency reduction


This scenario demonstrates:
βœ… Real business problem
βœ… Parallelism (not async hype)
βœ… Controlled concurrency
βœ… ExecutorService best usage
βœ… SLA-driven design


⚠ Common Mistakes (Say This in Interview)

❌ Creating new ExecutorService per request
❌ Unlimited thread pools
❌ Blocking everything blindly
❌ Ignoring timeouts
❌ No graceful shutdown

Top comments (0)