Backend Development 13 min read

Request Collapsing Techniques in Java: Hystrix Collapser, BatchCollapser, and ConcurrentHashMultiset

This article explains three Java request‑collapsing approaches—Hystrix Collapser, a custom BatchCollapser, and Guava's ConcurrentHashMultiset—detailing their implementations, configurations, and ideal usage scenarios to reduce downstream load and improve system throughput.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Request Collapsing Techniques in Java: Hystrix Collapser, BatchCollapser, and ConcurrentHashMultiset

Combining similar or duplicate requests upstream before sending them downstream can dramatically lower downstream load and increase overall system throughput; the article introduces three request‑collapsing techniques—Hystrix Collapser, ConcurrentHashMultiset, and a custom BatchCollapser—and compares the scenarios each best fits.

In typical request‑response models each request occupies its own thread and memory, and the I/O cost per request is high; merging many identical I/O operations into a single operation can significantly reduce the burden on downstream services.

The author spent considerable time comparing existing libraries, examined the hystrix javanica source, and implemented a simple collapser to address a niche business need, sharing the findings for others facing similar problems.

Hystrix Collapser is part of Netflix's open‑source Hystrix library, which provides circuit‑breaker functionality to keep web servers stable under high concurrency. To use request collapsing, projects must include hystrix-core and hystrix-javanica and configure the HystrixAspect bean, for example:

<aop:aspectj-autoproxy/>
<bean id="hystrixAspect" class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"/>

The collapser can be applied via annotations: add @HystrixCollapser on the method to be collapsed and @HystrixCommand on the batch method. The single method must accept a single argument (or a wrapper for multiple arguments) and return java.util.concurrent.Future<SingleReturn> , while the batch method receives a java.util.List<SingleParam> and returns a java.util.List<SingleReturn> with matching element counts.

A minimal example:

public class HystrixCollapserSample {
    @HystrixCollapser(batchMethod = "batch")
    public Future
single(String input) {
        return null; // single method is never executed directly
    }
    public List
batch(List
inputs) {
        return inputs.stream().map(it -> Boolean.TRUE).collect(Collectors.toList());
    }
}

Internally, Hystrix creates a collapser instance per scope, stores incoming request arguments in a concurrent map, creates an Observable for each request, and uses a timer thread to batch pending requests, invoke the batch method, and map results back to the original futures.

Configuration of a collapser is done on the @HystrixCollapser annotation and includes both specific and generic Hystrix command properties, for example:

@HystrixCollapser(
    batchMethod = "batch",
    collapserKey = "single",
    scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
    collapserProperties = {
        @HystrixProperty(name = "maxRequestsInBatch", value = "100"),
        @HystrixProperty(name = "timerDelayInMilliseconds", value = "1000"),
        @HystrixProperty(name = "requestCache.enabled", value = "true")
    })

BatchCollapser is a custom implementation created because the business did not need individual request results. It stores incoming requests in a LinkedBlockingDeque , triggers batch execution either when a timer fires or when the number of queued requests reaches a threshold, and returns success immediately without waiting for the batch result.

public class BatchCollapser
implements InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(BatchCollapser.class);
    private static final ScheduledExecutorService SCHEDULE_EXECUTOR = Executors.newScheduledThreadPool(1);
    private volatile LinkedBlockingDeque
batchContainer = new LinkedBlockingDeque<>();
    private Handler
, Boolean> cleaner;
    private long interval;
    private int threshHold;
    // constructor, afterPropertiesSet, submit, clean, getInstance ...
}

Key points of the implementation include a singleton factory based on the handler class, use of ScheduledExecutorService instead of java.util.Timer to avoid blocking issues, and safe draining of the queue via drainTo .

ConcurrentHashMultiset (from Guava) provides a thread‑safe multiset that counts duplicate elements instead of overwriting them. It is ideal for high‑frequency statistical scenarios where many identical items need to be aggregated before persisting.

if (ConcurrentHashMultiset.isEmpty()) {
    return;
}
List
transferList = Lists.newArrayList();
ConcurrentHashMultiset.elementSet().forEach(request -> {
    int count = ConcurrentHashMultiset.count(request);
    if (count <= 0) return;
    transferList.add(count == 1 ? request : new Request(request.getIncrement() * count));
    ConcurrentHashMultiset.remove(request, count);
});

In summary, use hystrix collapser when each request’s result is required despite added latency, BatchCollapser when results are unimportant and merging can be triggered by time or count, and ConcurrentHashMultiset for high‑repeat statistical workloads. Combining a custom BatchCollapser with a ConcurrentHashMultiset container can leverage the strengths of both approaches.

Recommended reading links are provided for further exploration of related performance and architecture topics.

backendJavaHystrixRequest CollapsingBatchCollapserConcurrentHashMultiset
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.