Common Java Application Issues and How to Solve Them
Java is renowned for its reliability, scalability, and cross-platform compatibility. However, even well-designed Java applications can encounter performance bottlenecks, stability issues, and operational challenges. Understanding the most common problems—and knowing how to diagnose and fix them—is essential for every Java developer.
Here are some of the most frequent Java application issues and proven solutions.
1. Memory Leaks
Symptoms
Gradually increasing heap usage
Frequent Full GC
OutOfMemoryError: Java heap space
Application slows down over time
Common Causes
Static collections holding references
Unclosed resources
Cache without eviction
Event listeners not removed
ThreadLocal variables not cleared
Solution
Use profiling tools:
Eclipse MAT (Memory Analyzer)
VisualVM
JProfiler
YourKit
Example:
private static final List<User> users = new ArrayList<>();
public void add(User user) {
users.add(user); // Never removed
}
Better approach:
Cache<String, User> cache =
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(30))
.build();
2. High CPU Usage
Symptoms
CPU remains above 80%
Slow response times
Threads consuming excessive CPU
Common Causes
Infinite loops
Busy waiting
Inefficient algorithms
Excessive logging
Recursive calls
Diagnosis
top -H
jstack <pid>
Use Java Flight Recorder (JFR) or Async Profiler to identify hotspots.
Bad example:
while (!finished) {
// do nothing
}
Better:
CountDownLatch.await();
3. OutOfMemoryError
Types
Java heap space
GC overhead limit exceeded
Metaspace
Direct buffer memory
Unable to create native thread
Solution
Increase heap:
-Xms4G
-Xmx4G
Enable heap dump:
-XX:+HeapDumpOnOutOfMemoryError
Analyze using Eclipse MAT.
4. Slow Garbage Collection
Symptoms
Long GC pauses
High latency
Low throughput
Causes
Small heap
Too many temporary objects
Large object allocations
Wrong GC algorithm
Solutions
Modern JDK:
G1GC
Low latency:
ZGC
Shenandoah
Monitor:
-Xlog:gc*
5. Thread Deadlocks
Symptoms
Application freezes.
Example:
Thread A:
lock1 -> lock2
Thread B:
lock2 -> lock1
Detection:
jstack
Solution:
Always acquire locks in the same order.
Better:
ReentrantLock.tryLock()
6. Thread Pool Exhaustion
Symptoms
Requests time out
Queue keeps growing
Low throughput
Bad configuration:
Executors.newFixedThreadPool(10)
Better:
ThreadPoolExecutor executor =
new ThreadPoolExecutor(
20,
100,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000)
);
Monitor:
Active threads
Queue size
Task rejection
7. Database Connection Pool Exhaustion
Symptoms
Cannot obtain JDBC Connection
Causes
Connections not closed
Pool too small
Long-running queries
Always use:
try(Connection conn = ds.getConnection()){
}
Recommended pools:
HikariCP
Apache DBCP
8. Slow SQL Queries
Symptoms
High database CPU
Slow API response
Lock contention
Diagnosis
EXPLAIN ANALYZE
Solutions
Add indexes
Avoid SELECT *
Pagination
Batch updates
Prepared Statements
9. Too Many HTTP Requests
Symptoms
Microservices become slow.
Problem:
Order Service
↓
Customer Service
↓
Address Service
↓
Payment Service
↓
Inventory Service
Solution
Caching
Async calls
API Gateway
Aggregation
Event-driven architecture
10. Serialization Problems
Example:
implements Serializable
Common issue:
InvalidClassException
Solution
private static final long serialVersionUID = 1L;
Or use JSON instead.
11. ClassLoader Memory Leaks
Often seen in:
Tomcat
Spring Boot DevTools
Application Servers
Cause:
Static references to application classes.
Solution:
Remove static caches
Close custom classloaders
Avoid classloader pinning
12. NullPointerException
Still the #1 Java exception.
Bad:
user.getAddress().getCity();
Better:
Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("Unknown");
13. ConcurrentModificationException
Bad:
for(User user : users){
users.remove(user);
}
Better:
Iterator<User> it = users.iterator();
while(it.hasNext()){
if(condition){
it.remove();
}
}
14. Microservice Timeout Cascades
Symptoms
One slow service causes the whole system to fail.
Solution
Timeouts
Circuit Breaker
Retry
Bulkhead
Rate Limiter
Example using Resilience4j:
@CircuitBreaker(name="payment")
15. Logging Overhead
Bad
logger.info("Result " + expensiveMethod());
Better
logger.info("Result {}", result);
Use async logging for high-throughput applications.
16. Large Object Allocation
Problem
byte[] data = new byte[500_000_000];
Solution
Stream processing
Buffer reuse
Chunk processing
17. Excessive Object Creation
Bad
for (...) {
new BigDecimal(...);
}
Better
Reuse immutable objects when possible.
18. Poor JVM Configuration
Recommended (Java 21)
-Xms4G
-Xmx4G
-XX:+UseG1GC
-XX:+HeapDumpOnOutOfMemoryError
-XX:+ExitOnOutOfMemoryError
-Xlog:gc*
19. Blocking Operations in Reactive Applications
Problem
Mono.just(loadFromDatabase());
Solution
Mono.fromCallable(() -> load())
.subscribeOn(Schedulers.boundedElastic());
20. Production Monitoring Gaps
Every Java application should monitor:
JVM Metrics
Heap usage
GC pauses
Thread count
CPU
Metaspace
Application Metrics
Request latency
Error rate
Throughput
Database latency
Cache hit ratio
Recommended tools:
Micrometer
Prometheus
Grafana
OpenTelemetry
Java Flight Recorder (JFR)
Best Practices Checklist
✅ Close all resources using try-with-resources.
✅ Use HikariCP for JDBC connection pooling.
✅ Choose the appropriate GC (G1GC, ZGC, or Shenandoah) based on latency requirements.
✅ Profile before optimizing—measure, don't guess.
✅ Set sensible timeouts for all network and database calls.
✅ Implement resilience patterns such as Circuit Breakers, Retries, and Bulkheads.
✅ Monitor JVM and application metrics continuously.
✅ Prefer asynchronous or non-blocking processing for I/O-intensive workloads.
✅ Tune thread pools and connection pools according to workload characteristics.
✅ Keep dependencies and the JDK up to date to benefit from performance and security improvements.
Final Thoughts
Most production issues in Java applications are not caused by the language itself but by resource management, concurrency, inefficient I/O, database interactions, and insufficient observability. By combining sound coding practices with proper JVM tuning, resilient architecture, and comprehensive monitoring, teams can build Java applications that remain fast, stable, and scalable under real-world production workloads.
For senior Java developers and architects, mastering these common issues—and the tools to diagnose them—is a key step toward building enterprise-grade systems that are resilient, performant, and easier to maintain.
