10 Java Performance Tips Every Developer Should Know

Practical techniques to optimize Java application performance

10 Java Performance Tips Every Developer Should Know

Java performance optimization is an art that combines deep JVM knowledge with practical programming techniques. Here are 10 essential tips I want to share with you.

1. Use StringBuilder for String Concatenation

❌ Incorrect:

String result = "";
for (int i = 0; i < 1000; i++) {
    result += "item" + i;
}

✅ Correct:

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append("item").append(i);
}
String result = sb.toString();

2. Optimize Collection Usage

ArrayList vs LinkedList

  • ArrayList: Better for random access
  • LinkedList: Better for frequent insertions/deletions
// For frequent index-based access
List<String> list = new ArrayList<>();

// For frequent insertions/deletions
List<String> list = new LinkedList<>();

HashMap vs TreeMap

// O(1) for basic operations
Map<String, Integer> hashMap = new HashMap<>();

// O(log n) but maintains order
Map<String, Integer> treeMap = new TreeMap<>();

3. Lazy Loading of Resources

public class ExpensiveResource {
    private static volatile ExpensiveResource instance;
    
    public static ExpensiveResource getInstance() {
        if (instance == null) {
            synchronized (ExpensiveResource.class) {
                if (instance == null) {
                    instance = new ExpensiveResource();
                }
            }
        }
        return instance;
    }
}

4. Object Pooling for Expensive Resources

public class DatabaseConnectionPool {
    private final Queue<Connection> pool = new ConcurrentLinkedQueue<>();
    private final int maxSize;
    
    public Connection getConnection() {
        Connection conn = pool.poll();
        if (conn == null) {
            conn = createNewConnection();
        }
        return conn;
    }
    
    public void returnConnection(Connection conn) {
        if (pool.size() < maxSize) {
            pool.offer(conn);
        } else {
            conn.close();
        }
    }
}

5. Use Streams Carefully

For simple operations, traditional loops can be faster:

// Stream (more readable but potentially slower)
list.stream()
    .filter(x -> x > 10)
    .mapToInt(x -> x * 2)
    .sum();

// Traditional loop (faster for simple operations)
int sum = 0;
for (int x : list) {
    if (x > 10) {
        sum += x * 2;
    }
}

6. JVM Optimization

Important parameters:

# Heap size
-Xms2g -Xmx4g

# Garbage Collector
-XX:+UseG1GC

# JIT Compiler
-XX:+UseCompressedOops

# Monitoring
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps

7. Avoid Unnecessary Autoboxing

❌ Incorrect:

Integer sum = 0;
for (int i = 0; i < 1000; i++) {
    sum += i; // Autoboxing in each iteration
}

✅ Correct:

int sum = 0;
for (int i = 0; i < 1000; i++) {
    sum += i;
}
Integer result = sum; // Autoboxing only once

8. Use Parallel Streams for CPU-Intensive Operations

// For large lists and expensive operations
list.parallelStream()
    .filter(this::expensiveFilter)
    .map(this::expensiveTransformation)
    .collect(Collectors.toList());

⚠️ Warning: Don’t use parallel streams for:

  • Small lists (< 1000 elements)
  • I/O bound operations
  • Very fast operations

9. Optimize Database Access

Batch Operations

// ❌ Multiple queries
for (User user : users) {
    userRepository.save(user);
}

// ✅ Batch insert
userRepository.saveAll(users);

Connection Pooling

@Configuration
public class DatabaseConfig {
    
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setMaximumPoolSize(20);
        config.setMinimumIdle(5);
        config.setConnectionTimeout(30000);
        return new HikariDataSource(config);
    }
}

10. Profiling and Monitoring

Essential tools:

  1. JProfiler: For detailed memory and CPU analysis
  2. VisualVM: Free tool included with JDK
  3. JConsole: For basic monitoring
  4. Micrometer: For metrics in Spring Boot applications

Example with Micrometer:

@Component
public class PerformanceMetrics {
    
    private final MeterRegistry meterRegistry;
    private final Timer.Sample sample;
    
    @EventListener
    public void handleMethodExecution(MethodExecutionEvent event) {
        Timer.Sample.start(meterRegistry)
            .stop(Timer.builder("method.execution")
                .tag("method", event.getMethodName())
                .register(meterRegistry));
    }
}

Benchmarking Tools

JMH (Java Microbenchmark Harness)

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class StringConcatenationBenchmark {
    
    @Benchmark
    public String stringBuilder() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 100; i++) {
            sb.append("test");
        }
        return sb.toString();
    }
    
    @Benchmark
    public String stringConcatenation() {
        String result = "";
        for (int i = 0; i < 100; i++) {
            result += "test";
        }
        return result;
    }
}

Conclusion

Java performance optimization requires a systematic approach:

  1. Measure first: Don’t optimize without data
  2. Identify bottlenecks: Use profilers
  3. Optimize the critical: Focus on the 20% that causes 80% of problems
  4. Verify improvements: Measure after each optimization

Remember: “Premature optimization is the root of all evil” - Donald Knuth

What optimization techniques have you found most effective in your projects?

↑↓ Navigate
Enter Select
Esc Close
Search across articles, projects, and site content