Skip to content

Expensive Callback

Rule details

Rule ID expensive-callback
Severity WARNING — likely performance problem
Confidence LOW — heuristic, check manually
Category Loop amplifiers
Complexity O(n × cost) → O(n)

Description

Detects expensive operations inside higher-order function callbacks like filter, map, forEach, and removeIf. These callbacks execute once per element — an expensive operation inside them multiplies across the entire collection.

Typical

List<Order> recent = orders.stream()
    .filter(o -> LocalDate.parse(o.getDateStr()).isAfter(cutoff))
    .collect(Collectors.toList());

LocalDate.parse() is called for every order. Parsing dates is expensive — this creates O(n) date parser invocations where pre-parsing or caching would avoid it.

List<Boolean> flags = items.stream()
    .map(item -> allowed.contains(item))  // linear lookup per item
    .collect(Collectors.toList());

List.contains() inside map makes this O(n × m).

After

// Pre-parse dates before filtering
Map<Order, LocalDate> parsed = orders.stream()
    .collect(Collectors.toMap(o -> o, o -> LocalDate.parse(o.getDateStr())));
List<Order> recent = orders.stream()
    .filter(o -> parsed.get(o).isAfter(cutoff))
    .collect(Collectors.toList());
// Convert to Set for O(1) lookups
Set<String> allowedSet = new HashSet<>(allowed);
List<Boolean> flags = items.stream()
    .map(allowedSet::contains)
    .collect(Collectors.toList());

What it catches

  • Date parsing/creationLocalDate.parse(), new Date(), SimpleDateFormat.parse()
  • Regex compilationPattern.compile(), new Regex()
  • Linear lookupsList.contains(), indexOf() on non-Set collections
  • Heavyweight object constructionnew ObjectMapper(), new Gson()
  • Cross-method patterns — expensive operations hidden in methods called from callbacks

Suggestion

Move expensive operations out of the callback — pre-compute, cache, or use a more efficient data structure