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

Published: (December 28, 2025 at 10:02 AM EST)
2 min read
Source: Dev.to

Source: Dev.to

🏦 Business Scenario (Very Common in FinTech)

Imagine a payment processing service.

Before processing a payment, the system must validate:

  • Account Status (active / blocked)
  • Balance Check
  • 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

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);
    }
}

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 accountCheck() {
        return () -> {
            Thread.sleep(500); // simulate service call
            return true; // account is active
        };
    }

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

    public Callable fraudCheck() {
        return () -> {
            Thread.sleep(800); // simulate service call
            return true; // low risk
        };
    }
}

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> results = executorService.invokeAll(
                List.of(
                        tasks.accountCheck(),
                        tasks.balanceCheck(),
                        tasks.fraudCheck()
                )
        );

        // If any validation fails → reject payment
        for (Future result : results) {
            if (!result.get()) {
                return false;
            }
        }
        return true;
    }
}

🔍 Why invokeAll()?

  • Submits multiple tasks together
  • Waits until all complete
  • Clean & readable 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";
    }
}

5️⃣ curl Request

curl -X POST http://localhost:8080/validate-payment

6️⃣ Response

Payment validation successful

⏱ Performance Comparison

ApproachApprox. Time
Sequential~2.0 sec
ExecutorService (parallel)~0.8 sec

60 %+ latency reduction

This scenario demonstrates:

  • Real business problem
  • Parallelism (not just async hype)
  • Controlled concurrency
  • Best usage of ExecutorService
  • SLA‑driven design

⚠ Common Mistakes (Say This in Interview)

  • Creating a new ExecutorService per request
  • Using unlimited thread pools
  • Blindly blocking everything
  • Ignoring timeouts
  • Not performing a graceful shutdown
Back to Blog

Related posts

Read more »