Backend Development 8 min read

Request Merging in Java: Concept, Pros & Cons, and Implementation with ScheduledExecutorService

This article explains the concept of request merging for high‑concurrency web services, outlines its advantages and drawbacks, and provides a complete Java implementation using ScheduledExecutorService, a memory queue, and a generic BatchCollapser utility with usage examples.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Request Merging in Java: Concept, Pros & Cons, and Implementation with ScheduledExecutorService

In web projects, each HTTP request is normally processed individually, which can cause excessive I/O under high concurrency; merging multiple requests into a single batch request can reduce the number of interactions and improve performance.

The main benefit of request merging is that it aggregates several calls, allowing a timed or count‑based delay before sending a single batch request, thereby decreasing I/O overhead. The downside is the introduced latency, making it unsuitable for time‑critical APIs.

The implementation uses a ScheduledExecutorService together with an in‑memory LinkedBlockingDeque to collect incoming requests. When the queue size reaches a configured count threshold or a time interval expires, the accumulated requests are drained and processed by a user‑provided BatchHandler .

package com.leilei.support;

import lombok.extern.log4j.Log4j2;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @author lei
 * @desc Request merging utility class
 */
@Log4j2
public class BatchCollapser
{
    private static final Map
BATCH_INSTANCE = new ConcurrentHashMap<>();
    private static final ScheduledExecutorService SCHEDULE_EXECUTOR = Executors.newScheduledThreadPool(1);

    private final LinkedBlockingDeque
batchContainer = new LinkedBlockingDeque<>();
    private final BatchHandler
, R> handler;
    private final int countThreshold;

    private BatchCollapser(BatchHandler
, R> handler, int countThreshold, long timeThreshold) {
        this.handler = handler;
        this.countThreshold = countThreshold;
        SCHEDULE_EXECUTOR.scheduleAtFixedRate(() -> {
            try {
                this.popUpAndHandler(BatchHandlerType.BATCH_HANDLER_TYPE_TIME);
            } catch (Exception e) {
                log.error("pop-up container exception", e);
            }
        }, timeThreshold, timeThreshold, TimeUnit.SECONDS);
    }

    public void addRequestParam(T event) {
        batchContainer.add(event);
        if (batchContainer.size() >= countThreshold) {
            popUpAndHandler(BatchHandlerType.BATCH_HANDLER_TYPE_DATA);
        }
    }

    private void popUpAndHandler(BatchHandlerType handlerType) {
        List
tryHandlerList = Collections.synchronizedList(new ArrayList<>(countThreshold));
        batchContainer.drainTo(tryHandlerList, countThreshold);
        if (tryHandlerList.isEmpty()) return;
        try {
            R handle = handler.handle(tryHandlerList, handlerType);
            log.info("Batch execution result:{}", handle);
        } catch (Exception e) {
            log.error("batch execute error, transferList:{}", tryHandlerList, e);
        }
    }

    public static
BatchCollapser
getInstance(BatchHandler
, R> batchHandler, int countThreshold, long timeThreshold) {
        Class jobClass = batchHandler.getClass();
        if (BATCH_INSTANCE.get(jobClass) == null) {
            synchronized (BatchCollapser.class) {
                BATCH_INSTANCE.putIfAbsent(jobClass, new BatchCollapser<>(batchHandler, countThreshold, timeThreshold));
            }
        }
        return BATCH_INSTANCE.get(jobClass);
    }

    public interface BatchHandler
{
        R handle(T input, BatchHandlerType handlerType);
    }

    public enum BatchHandlerType {
        BATCH_HANDLER_TYPE_DATA,
        BATCH_HANDLER_TYPE_TIME,
    }
}

To use the utility, a service implements BatchCollapser.BatchHandler , obtains a singleton instance via BatchCollapser.getInstance with desired thresholds (e.g., 20 requests or 5 seconds), and adds request parameters through addRequestParam . A scheduled method simulates incoming requests and demonstrates the merging behavior.

package com.leilei.support;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.List;

@Service
public class ProductService implements BatchCollapser.BatchHandler
, Integer> {
    private BatchCollapser
batchCollapser;

    @PostConstruct
    private void postConstructorInit() {
        // Merge when 20 requests are collected or every 5 seconds
        batchCollapser = BatchCollapser.getInstance(this, 20, 5);
    }

    @Override
    public Integer handle(List
input, BatchCollapser.BatchHandlerType handlerType) {
        System.out.println("Handler type:" + handlerType + ", batch params:" + input);
        return input.stream().mapToInt(x -> x).sum();
    }

    @Scheduled(fixedDelay = 300)
    public void generateRequest() {
        Integer requestParam = (int) (Math.random() * 100) + 1;
        batchCollapser.addRequestParam(requestParam);
        System.out.println("Current request param:" + requestParam);
    }
}

A simple Product data class is also shown for completeness.

@Data
public class Product {
    private Integer id;
    private String notes;
}

The provided code is a demonstration; developers can extend it, adjust thresholds, and integrate it into real services to alleviate server pressure during high‑traffic periods.

BackendJavaperformance optimizationbatch processingrequest mergingScheduledExecutorService
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.