DEV Community

Cover image for Common Java Mistakes Developers Make
Madhan Kumar
Madhan Kumar

Posted on

Common Java Mistakes Developers Make

Discover the most frequent Java errors with simple explanations, practical code examples, and tips to improve your code quality:

Introduction:

You may have many years of experience writing Java, but mistakes can still happen. Some bugs do not show up right away. They stay hidden in your code for a long time and only appear when the application is running in production. These quiet issues can be hard to notice until they cause real problems.

In this article, we will look at common mistakes that even experienced Java developers often make. Each mistake includes a simple code example and an easy way to fix it. Whether you are working on a new project or checking older code, this guide will help you catch these issues early and write better Java programs.

Frequent mistakes that slowly sneak into well-established code:

1. Trusting That Nulls Won’t Happen:

// ❌ Throws NullPointerException if any part is null
String name = user.getProfile().getName().toUpperCase();

// ✅ Check for null step-by-step or use Optional
String name = Optional.ofNullable(user)
        .map(User::getProfile)
        .map(Profile::getName)
        .map(String::toUpperCase)
        .orElse("UNKNOWN");
Enter fullscreen mode Exit fullscreen mode

Tip: Always assume something can be null unless you’re 100% sure.

2. Using Optional Inside Your Classes:

// ❌ Not a good practice
class User {
    Optional<String> phoneNumber;  // messy and harder to use
}

// ✅ Use Optional only in method return types
class User {
    String phoneNumber;  // keep it simple
}
Optional<String> getPhoneNumber(User user) {  }
Enter fullscreen mode Exit fullscreen mode

Tip: Use Optional only for method return types and not as fields.

3. Forgetting to Close Files or Streams:

// ❌ File might stay open if there's an error
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
reader.close();  // may never be called

// ✅ Auto-close using try-with-resources
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    String line = reader.readLine();
}
Enter fullscreen mode Exit fullscreen mode

Tip: Use try-with-resources to auto-close files and connections.

4. Locking Code for Too Long:

// ❌ Lock includes slow database call
synchronized (cache) {
    cache.put(id, getDataFromDatabase(id));  // slow call inside lock
}

// ✅ Only lock shared data access
Data data = getDataFromDatabase(id);  // outside lock
synchronized (cache) {
    cache.putIfAbsent(id, data);
}
Enter fullscreen mode Exit fullscreen mode

Tip: Keep your lock block short. Don’t put slow code inside it.

5. Using the Default Thread Pool Without Limits:

// ❌ Can create too many threads
ExecutorService pool = Executors.newCachedThreadPool();

// ✅ Create a thread pool with limits
ExecutorService pool = Executors.newFixedThreadPool(10);
Enter fullscreen mode Exit fullscreen mode

Tip: Always control the number of threads to avoid memory issues.

6. Catching Every Exception (and Doing Nothing):

// ❌ Hides the real problem
try {
    processPayment();
} catch (Exception e) {
    // do nothing
}

// ✅ Handle specific problems
try {
    processPayment();
} catch (IOException e) {
    System.out.println("Could not connect to server");
}
Enter fullscreen mode Exit fullscreen mode

Tip: Catch only the exceptions you know how to handle.

7. Forgetting equals() and hashCode():

// ❌ Breaks HashMap and Set usage
class Point {
    int x, y;
}

// ✅ Use records or generate equals/hashCode
record Point(int x, int y) { }
Enter fullscreen mode Exit fullscreen mode

Tip: Always write equals() and hashCode() if you want to use the object in a Set or as a Map key.

8. Loading Classes the Wrong Way:

// ❌ Can cause ClassCastException
Class<?> clazz = pluginClassLoader.loadClass("com.example.Helper");
Object obj = clazz.getDeclaredConstructor().newInstance();
Helper h = (Helper) obj;  // error here

// ✅ Use the main class loader when possible
Class<?> clazz = Class.forName("com.example.Helper");
Helper h = (Helper) clazz.getDeclaredConstructor().newInstance();
Enter fullscreen mode Exit fullscreen mode

Tip: Be careful when using multiple class loaders, especially with plugins or frameworks.

9. Writing Your Own Benchmarks:

// ❌ Not reliable
long start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
    doSomething();
}
long end = System.nanoTime();
System.out.println("Time: " + (end - start));

// ✅ Use JMH (Java Microbenchmark Harness)
@Benchmark
public void testMethod() {
    doSomething();
}
Enter fullscreen mode Exit fullscreen mode

Tip: Use JMH for accurate performance testing.

10. Using New Features Without a Good Reason:

// ❌ Confusing use of new feature
if (obj instanceof String s && s.isBlank()) {
    System.out.println("Blank string");
}

// ✅ Use what’s easy to read
if (obj instanceof String) {
    String s = (String) obj;
    if (s.isBlank()) {
        System.out.println("Blank string");
    }
}
Enter fullscreen mode Exit fullscreen mode

Tip: Don’t use new syntax unless it really makes your code better.

11. Mixing up == and equals():

// ❌ Compares object reference, not content
String a = new String("Java");
String b = new String("Java");

if (a == b) {
    System.out.println("Same");  // false
}

// ✅ Use equals() to compare values
if (a.equals(b)) {
    System.out.println("Same");  // true
}
Enter fullscreen mode Exit fullscreen mode

Tip: Use == for primitives, equals() for objects.

12. Not Making Classes Immutable When Needed:

// ❌ Mutable class can cause bugs
class Account {
    public double balance;
}

// ✅ Immutable class is safer
final class Account {
    private final double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }
}
Enter fullscreen mode Exit fullscreen mode

Tip: Use final, private fields, and no setters to make a class immutable.

13. Overusing Static:

// ❌ Hard to test or extend
public static String formatName(String name) {
    return name.toUpperCase();
}

// ✅ Use instance methods when logic may change
public class NameFormatter {
    public String format(String name) {
        return name.toUpperCase();
    }
}
Enter fullscreen mode Exit fullscreen mode

Tip: Use static only for utility methods that don’t depend on state.

14. Returning Null Instead of Empty Collections:

// ❌ Forces null checks every time
public List<String> getItems() {
    return null;
}

// ✅ Return an empty list instead
public List<String> getItems() {
    return Collections.emptyList();
}
Enter fullscreen mode Exit fullscreen mode

Tip: Return Collections.emptyList() or Optional.empty() to avoid NullPointerException.

15. Using Raw Types with Generics:

// ❌ No type safety
List list = new ArrayList();
list.add("Hello");
Integer num = (Integer) list.get(0);  // ClassCastException

// ✅ Use generics for safety
List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0);  // safe
Enter fullscreen mode Exit fullscreen mode

Tip: Always use generics like List, Map.

16. Ignoring Compiler Warnings:

// ❌ Warning ignored: unchecked cast
List rawList = new ArrayList();
List<String> names = (List<String>) rawList;  // unsafe

// ✅ Fix the root cause, don’t suppress blindly
List<String> names = new ArrayList<>();
Enter fullscreen mode Exit fullscreen mode

Tip: Warnings are not just suggestions, they often save you from bugs.

17. Having Too Many Responsibilities in One Class:

// ❌ One class does everything: DB, UI, logic
class UserManager {
    void saveUser() {}
    void renderForm() {}
    void sendEmail() {}
}

// ✅ Break into smaller classes
class UserRepository { void saveUser() {} }
class UserForm { void renderForm() {} }
class EmailService { void sendEmail() {} }
Enter fullscreen mode Exit fullscreen mode

Tip: Follow the “Single Responsibility Principle” in Solid Principles.

18. Catching Throwable or Error:

// ❌ Dangerous: catches everything, even serious errors
try {
    riskyCode();
} catch (Throwable t) {
    // Bad idea
}

// ✅ Catch only what you expect
try {
    riskyCode();
} catch (IOException e) {
    System.out.println("IO error occurred");
}
Enter fullscreen mode Exit fullscreen mode

Tip: Never catch Throwable or Error. Catch specific exceptions only.

19. Using Magic Numbers in Code:

// ❌ What does 86400 mean?
if (seconds == 86400) {
    System.out.println("One day");
}

// ✅ Use constants for clarity
public static final int SECONDS_IN_A_DAY = 86400;

if (seconds == SECONDS_IN_A_DAY) {
    System.out.println("One day");
}
Enter fullscreen mode Exit fullscreen mode

Tip: Use named constants instead of random numbers.

20. Ignoring Thread Safety in Shared Objects:

// ❌ Not thread-safe
public class Counter {
    public int count = 0;

    public void increment() {
        count++;
    }
}

// ✅ Make it thread-safe
public class Counter {
    private final AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }
}
Enter fullscreen mode Exit fullscreen mode

Tip: Use thread-safe classes like AtomicInteger, or use synchronization properly.

21. Using System.out.println for Logging:

// ❌ Not good for real applications
System.out.println("User logged in");

// ✅ Use a proper logging framework
private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
logger.info("User logged in");
Enter fullscreen mode Exit fullscreen mode

Tip: Use SLF4J with Logback or Log4j for better log control.

22. Ignoring Proper Access Modifiers:

// ❌ Everything is public
public class BankAccount {
    public double balance;
}

// ✅ Limit access as much as possible
public class BankAccount {
    private double balance;

    public double getBalance() {
        return balance;
    }
}
Enter fullscreen mode Exit fullscreen mode

Tip: Use private by default, then open up only when needed.

23. Writing Long Methods:

// ❌ Hard to read and maintain
public void processOrder(Order order) {
    validate(order);
    calculateTotal(order);
    updateStock(order);
    sendInvoice(order);
    notifyCustomer(order);
}

// ✅ Break into smaller methods
public void processOrder(Order order) {
    validate(order);
    handlePayment(order);
    notifyCustomer(order);
}
Enter fullscreen mode Exit fullscreen mode

Tip: Keep methods short and focused and ideally 5–15 lines.

24. Ignoring Overflows in Math:

// ❌ Might overflow silently
int total = 1_000_000 * 3_000;

// ✅ Use Math.multiplyExact() for safety
int total = Math.multiplyExact(1_000_000, 3_000);
Enter fullscreen mode Exit fullscreen mode

Tip: Use methods like Math.addExact() and Math.multiplyExact() to catch overflow errors.

25. Not Using Enum When You Should:

// ❌ Magic strings can cause typos
if (status.equals("PENDING")) {  }

// ✅ Use enums for safe and clear choices
enum OrderStatus { PENDING, CONFIRMED, CANCELLED }
if (status == OrderStatus.PENDING) {  }
Enter fullscreen mode Exit fullscreen mode

Tip: Enums give better type safety and help avoid bugs caused by typos.

26. Forgetting to Use StringBuilder in Loops:

// ❌ Slow: creates many string objects
String result = "";
for (String word : words) {
    result += word;
}

// ✅ Faster and memory-friendly
StringBuilder sb = new StringBuilder();
for (String word : words) {
    sb.append(word);
}
String result = sb.toString();
Enter fullscreen mode Exit fullscreen mode

Tip: Use StringBuilder for string concatenation in loops.

27. Not Validating Inputs:

// ❌ No input checks
public void setAge(int age) {
    this.age = age;
}

// ✅ Add input validation
public void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("Invalid age");
    }
    this.age = age;
}
Enter fullscreen mode Exit fullscreen mode

Tip: Always validate method arguments, especially in public APIs.

28. Misusing Final Variables:

// ❌ Final does not mean immutable
final List<String> names = new ArrayList<>();
names.add("Alice");  // This is allowed

// ✅ Make the content unmodifiable too
List<String> names = List.of("Alice", "Bob");  // truly immutable
Enter fullscreen mode Exit fullscreen mode

Tip: final stops reassignment, not mutation. Use immutable collections for safety.

29. Not Writing Unit Tests for Edge Cases:

// ❌ Only tests normal cases
@Test
void testDivide() {
    assertEquals(2, Calculator.divide(10, 5));
}

// ✅ Test edge cases too
@Test
void testDivideByZero() {
    assertThrows(ArithmeticException.class, () -> Calculator.divide(10, 0));
}
Enter fullscreen mode Exit fullscreen mode

Tip: Test happy paths and also test what happens when things go wrong.

30. Confusing Arrays.asList() with a Modifiable List:

// ❌ UnsupportedOperationException when modifying
List<String> list = Arrays.asList("apple", "banana", "cherry");
list.add("mango");  // 💥 Runtime error!

// ✅ Create a modifiable list
List<String> list = new ArrayList<>(Arrays.asList("apple", "banana", "cherry"));
list.add("mango");  // ✅ Works fine
Enter fullscreen mode Exit fullscreen mode

Tip: Use new ArrayList<>(...) if you need to modify the listArrays.asList() is good for read-only access or fixed-size lists.

Final Words:

No matter how many years you have spent working with Java, mistakes are still part of the journey. Many of these mistakes do not cause loud crashes. Instead, they stay hidden and show up much later when they are harder to trace and fix.

The best way to avoid them is to keep your code clear and simple. Test your logic often, review your changes carefully, and stay curious about how things can break. A little extra attention today can save hours of confusion tomorrow. Writing clean and reliable Java is not just about skill, it is about habits, patience, and always learning.

Top comments (0)