Skip to content
Development
Skill

/performance-smell-detection

Detect potential code-level performance smells in Java - streams, collections, boxing, regex, object creation. Provides awareness, not absolutes - always measure before optimizing. For JPA/database performance, use jpa-patterns instead.

From plugin
claude-code-java
70018 skills
Install
$ npx -y skills add decebals/claude-code-java --skill performance-smell-detection --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition โ†’
  • You can call itInvoke it directly when you want it.
  • Slash command/performance-smell-detection

Context preview

The summary Claude sees to decide when to auto-load this skill.

Detect potential code-level performance smells in Java - streams, collections, boxing, regex, object creation. Provides awareness, not absolutes - always measure before optimizing. For JPA/database performance, use jpa-patterns instead.

SKILL.md

performance-smell-detection.SKILL.md
name: performance-smell-detection
description: Detect potential code-level performance smells in Java - streams, collections, boxing, regex, object creation. Provides awareness, not absolutes - always measure before optimizing. For JPA/database performance, use jpa-patterns instead.

Performance Smell Detection Skill

Identify **potential** code-level performance issues in Java code.

Philosophy

> "Premature optimization is the root of all evil" - Donald Knuth

This skill helps you **notice** potential performance smells, not blindly "fix" them. Modern JVMs (Java 21/25) are highly optimized. Always:

1. **Measure first** - Use JMH, profilers, or production metrics 2. **Focus on hot paths** - 90% of time spent in 10% of code 3. **Consider readability** - Clear code often matters more than micro-optimizations

When to Use

  • Reviewing performance-critical code paths
  • Investigating measured performance issues
  • Learning about Java performance patterns
  • Code review with performance awareness

Scope

**This skill:** Code-level performance (streams, collections, objects) **For database:** Use `jpa-patterns` skill (N+1, lazy loading, pagination) **For architecture:** Use `architecture-review` skill

---

Quick Reference: Potential Smells

| Smell | Severity | Context | |-------|----------|---------| | Regex compile in loop | ๐Ÿ”ด High | Always worth fixing | | String concat in loop | ๐ŸŸก Medium | Still valid in Java 21/25 | | Stream in tight loop | ๐ŸŸก Medium | Depends on collection size | | Boxing in hot path | ๐ŸŸก Medium | Measure first | | Unbounded collection | ๐Ÿ”ด High | Memory risk | | Missing collection capacity | ๐ŸŸข Low | Minor, measure if critical |

---

String Operations (Java 9+ / 21 / 25)

What Changed

Since **Java 9** (JEP 280), string concatenation with `+` uses `invokedynamic`, not StringBuilder. The JVM optimizes simple concatenation well.

**Java 25** adds String::hashCode constant folding for additional optimization in Map lookups with String keys.

Still Valid: StringBuilder in Loops

// ๐Ÿ”ด Still problematic - new String each iteration
String result = "";
for (String s : items) {
    result += s;  // O(nยฒ) - creates n strings
}

// โœ… StringBuilder for loops
StringBuilder sb = new StringBuilder();
for (String s : items) {
    sb.append(s);
}
String result = sb.toString();

// โœ… Or use String.join / Collectors.joining
String result = String.join("", items);

Now Fine: Simple Concatenation

// โœ… Fine in Java 9+ - JVM optimizes this
String message = "User " + name + " logged in at " + timestamp;

// โœ… Also fine
return "Error: " + code + " - " + description;

Avoid in Hot Paths: String.format

// ๐ŸŸก String.format has parsing overhead
log.debug(String.format("Processing %s with id %d", name, id));

// โœ… Parameterized logging (SLF4J)
log.debug("Processing {} with id {}", name, id);

---

Stream API (Nuanced View)

The Reality

Streams have overhead, but it's **often acceptable**:

  • **< 100 items**: Streams can be 2-5x slower (but still microseconds)
  • **1K-10K items**: Difference narrows significantly
  • **> 10K items**: Often within 50% of loops
  • **GraalVM**: Can optimize streams to match loops

**Recommendation**: Prefer streams for readability. Optimize to loops only when profiling shows a bottleneck.

When Streams Are Problematic

// ๐Ÿ”ด Stream created per iteration in hot loop
for (int i = 0; i < 1_000_000; i++) {
    boolean found = items.stream()
        .anyMatch(item -> item.getId() == i);
}

// โœ… Pre-compute lookup structure
Set<Integer> itemIds = items.stream()
    .map(Item::getId)
    .collect(Collectors.toSet());

for (int i = 0; i < 1_000_000; i++) {
    boolean found = itemIds.contains(i);
}

When Streams Are Fine

// โœ… Single pass, readable, not in tight loop
List<String> names = users.stream()
    .filter(User::isActive)
    .map(User::getName)
    .sorted()
    .collect(Collectors.toList());

// โœ… Primitive streams avoid boxing
int sum = numbers.stream()
    .mapToInt(Integer::intValue)
    .sum();

Parallel Streams: Use Carefully

// ๐Ÿ”ด Parallel on small collection - overhead > benefit
smallList.parallelStream().map(...);  // < 10K items

// ๐Ÿ”ด Parallel with shared mutable state
List<String> results = new ArrayList<>();
items.parallelStream()
    .forEach(results::add);  // Race condition!

// โœ… Parallel for CPU-intensive + large collections
List<Result> results = largeDataset.parallelStream()  // > 10K items
    .map(this::expensiveCpuComputation)
    .collect(Collectors.toList());

---

Boxing/Unboxing

Still a Real Issue

Boxing creates objects on heap, adds GC pressure. JVM caches small values (-128 to 127) but not larger ones.

> **Future**: Project Valhalla will improve this significantly.

// ๐Ÿ”ด Boxing in tight loop - creates millions of objects
Long sum = 0L;
for (int i = 0; i < 1_000_000; i++) {
    sum += i;  // Unbox, add, box
}

// โœ… Primitive
long sum = 0L;
for (int i = 0; i < 1_000_000; i++) {
    sum += i;
}

Use Primitive Streams

// ๐ŸŸก Boxing overhead
int sum = list.stream()
    .reduce(0, Integer::sum);

// โœ… Primitive stream
int sum = list.stream()
    .mapToInt(Integer::intValue)
    .sum();

---

Regex

Always Pre-compile in Loops

This advice is **not outdated** - Pattern.compile is expensive.

// ๐Ÿ”ด Compiles pattern every iteration
for (String input : inputs) {
    if (input.matches("\\d{3}-\\d{4}")) {  // Compiles regex!
        process(input);
    }
}

// โœ… Pre-compile
private static final Pattern PHONE = Pattern.compile("\\d{3}-\\d{4}");

for (String input : inputs) {
    if (PHONE.matcher(input).matches()) {
        process(input);
    }
}

---

Collections

Capacity Hint (Minor Optimization)

// ๐ŸŸข Low severity - but free optimization if size known
List<User> users = new ArrayList<>(expectedSize);
Map<String, User> map = new HashMap<>(expect
Read more
Ships withclaude-code-java

Reusable AI development infrastructure for Java projects, optimized for Claude Code This project is not affiliated with Anthropic.

Get the whole plugin

Other skills on claude-code-java.