20 Java Backend Pitfalls You Must Avoid

This article compiles and explains twenty frequent Java backend development mistakes—from classic NullPointer exceptions and incorrect date formatting to BigDecimal precision issues, improper use of ConcurrentHashMap, ThreadLocal data leakage, misuse of Arrays.asList, transaction pitfalls, and serialization quirks—providing code examples and best‑practice solutions to help developers write safer, more reliable code.

macrozheng
macrozheng
macrozheng
20 Java Backend Pitfalls You Must Avoid

Preface

Recently I read GeekTime's "100 Common Java Business Development Errors" and combined it with some pitfalls I encountered, writing this summary to help everyone.

1. Six Typical NullPointer Issues

Wrapper type NullPointer

Chained call NullPointer

Equals method left NullPointer

ConcurrentHashMap does not support null keys or values

Directly accessing collection or array elements

Directly accessing object properties

1.1 Wrapper type NullPointer

public class NullPointTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(testInteger(null));
    }
    private static Integer testInteger(Integer i) {
        return i + 1; // wrapper type may be null, causing NullPointerException
    }
}

1.2 Chained call NullPointer

public class NullPointTest {
    public static void main(String[] args) {
        // fruitService.getAppleService() may be null, causing NullPointerException
        fruitService.getAppleService().getWeight().equals("OK");
    }
}

1.3 Equals method left NullPointer

public class NullPointTest {
    public static void main(String[] args) {
        String s = null;
        if (s.equals("666")) { // s may be null, causing NullPointerException
            System.out.println("Public account: ...");
        }
    }
}

1.4 ConcurrentHashMap does not support null keys/values

public class NullPointTest {
    public static void main(String[] args) {
        Map map = new ConcurrentHashMap<>();
        String key = null;
        String value = null;
        map.put(key, value);
    }
}

1.5 Directly accessing collection or array elements

public class NullPointTest {
    public static void main(String[] args) {
        int[] array = null;
        List list = null;
        System.out.println(array[0]); // NullPointerException
        System.out.println(list.get(0)); // NullPointerException
    }
}

1.6 Directly accessing object properties

public class NullPointTest {
    public static void main(String[] args) {
        User user = null;
        System.out.println(user.getAge()); // NullPointerException
    }
}

2. Date format "YYYY" pitfall

Using uppercase "YYYY" in SimpleDateFormat can produce unexpected year values because it is week‑based.

Calendar calendar = Calendar.getInstance();
calendar.set(2019, Calendar.DECEMBER, 31);
Date testDate = calendar.getTime();
SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd");
System.out.println("2019-12-31 -> " + dtf.format(testDate));

Result: 2020-12-31 Correct approach: use lowercase yyyy.

SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("2019-12-31 -> " + dtf.format(testDate));

3. Money calculation precision pitfall

Floating‑point arithmetic leads to inaccurate results.

public class DoubleTest {
    public static void main(String[] args) {
        System.out.println(0.1+0.2);
        System.out.println(1.0-0.8);
        System.out.println(4.015*100);
        System.out.println(123.3/100);
        double amount1 = 3.15;
        double amount2 = 2.10;
        if (amount1 - amount2 == 1.05) {
            System.out.println("OK");
        }
    }
}

Result shows precision loss. Use BigDecimal with string constructors:

System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
System.out.println(new BigDecimal("1.0").subtract(new BigDecimal("0.8")));
System.out.println(new BigDecimal("4.015").multiply(new BigDecimal("100")));
System.out.println(new BigDecimal("123.3").divide(new BigDecimal("100")));

4. FileReader default charset causing garbled text

public class FileReaderTest {
    public static void main(String[] args) throws IOException {
        Files.deleteIfExists(Paths.get("jay.txt"));
        Files.write(Paths.get("jay.txt"), "你好,捡田螺的小男孩".getBytes(Charset.forName("GBK")));
        System.out.println("System default charset:" + Charset.defaultCharset());
        char[] chars = new char[10];
        String content = "";
        try (FileReader fileReader = new FileReader("jay.txt")) {
            int count;
            while ((count = fileReader.read(chars)) != -1) {
                content += new String(chars, 0, count);
            }
        }
        System.out.println(content);
    }
}

Result shows garbled characters because FileReader uses the JVM default charset (UTF‑8). Correct approach uses InputStreamReader with explicit charset:

try (FileInputStream fis = new FileInputStream("jay.txt");
     InputStreamReader isr = new InputStreamReader(fis, Charset.forName("GBK"))) {
    int count;
    while ((count = isr.read(chars)) != -1) {
        content += new String(chars, 0, count);
    }
}

5. Integer cache pitfall

public class IntegerTest {
    public static void main(String[] args) {
        Integer a = 127;
        Integer b = 127;
        System.out.println("a==b:" + (a == b)); // true
        Integer c = 128;
        Integer d = 128;
        System.out.println("c==d:" + (c == d)); // false
    }
}

Values between -128 and 127 are cached. JVM parameter -XX:AutoBoxCacheMax=1000 can extend the range.

6. Static variable depending on Spring bean

private static SmsService smsService = SpringContextUtils.getBean(SmsService.class);

This may be null due to class‑loading order. Safer lazy retrieval:

private static SmsService smsService = null;
public static SmsService getSmsService() {
    if (smsService == null) {
        smsService = SpringContextUtils.getBean(SmsService.class);
    }
    return smsService;
}

7. ThreadLocal reuse causing data leakage

private static final ThreadLocal<Integer> currentUser = ThreadLocal.withInitial(() -> null);
@GetMapping("wrong")
public Map wrong(@RequestParam("userId") Integer userId) {
    String before = Thread.currentThread().getName() + ":" + currentUser.get();
    currentUser.set(userId);
    String after = Thread.currentThread().getName() + ":" + currentUser.get();
    Map result = new HashMap();
    result.put("before", before);
    result.put("after", after);
    return result;
}

Because Tomcat reuses worker threads, previous request data may remain. Fix by clearing ThreadLocal in a finally block:

@GetMapping("right")
public Map right(@RequestParam("userId") Integer userId) {
    String before = Thread.currentThread().getName() + ":" + currentUser.get();
    currentUser.set(userId);
    try {
        String after = Thread.currentThread().getName() + ":" + currentUser.get();
        Map result = new HashMap();
        result.put("before", before);
        result.put("after", after);
        return result;
    } finally {
        currentUser.remove();
    }
}

8. Switch fall‑through pitfall

public class SwitchTest {
    private static String testSwitch(String key) {
        switch (key) {
            case "1":
                System.out.println("1");
            case "2":
                System.out.println(2);
                return "2";
            case "3":
                System.out.println("3");
            default:
                System.out.println("return default");
                return "4";
        }
    }
}

Without break, execution falls through to the next case until a return or break is encountered.

9. Arrays.asList pitfalls

9.1 Primitive arrays become a single element

int[] array = {1, 2, 3};
List list = Arrays.asList(array);
System.out.println(list.size()); // 1

9.2 Returned list does not support add/remove

String[] array = {"1", "2", "3"};
List list = Arrays.asList(array);
list.add("5"); // throws UnsupportedOperationException

9.3 Modifying the original array affects the list

String[] arr = {"1", "2", "3"};
List list = Arrays.asList(arr);
arr[1] = "4";
System.out.println(Arrays.toString(arr)); // [1, 4, 3]
System.out.println(list); // [1, 4, 3]

Wrap with new ArrayList(Arrays.asList(arr)) to avoid these issues.

10. ArrayList.toArray() cast pitfall

List<String> list = new ArrayList<>(1);
list.add("...");
String[] array21 = (String[]) list.toArray(); // ClassCastException

Use list.toArray(new String[0]) instead.

11. Exception handling pitfalls

11.1 Losing stack trace

try { readFile(); } catch (IOException e) { throw new RuntimeException("System busy"); }

Correct:

log.error("File read error", e); throw new RuntimeException("System busy");

11.2 Defining exceptions as static variables

throw Exceptions.ONEORTWO; // may reuse same instance

Correct:

throw new BusinessException("Business error", 0001);

11.3 Avoid e.printStackTrace() in production

log.error("Exception", e);

11.4 Handling exceptions in thread pool tasks

Submit swallows exceptions. Solutions: try/catch inside task, use Future.get(), set UncaughtExceptionHandler, override afterExecute.

11.5 Finally block re‑throwing hides original exception

try { throw new RuntimeException("try"); } finally { throw new RuntimeException("finally"); }

Handle cleanup in finally without re‑throwing, or catch and log the exception.

12. JSON serialization Long becomes Integer

Long idValue = 3000L;
Map<String, Object> data = new HashMap<>();
data.put("id", idValue);
String jsonString = JSON.toJSONString(data);
Map map = JSON.parseObject(jsonString, Map.class);
Object idObj = map.get("id"); // instance of Integer

JSON has no explicit Long type; numbers within Integer range are deserialized as Integer. Use string representation or custom serializer.

13. Executors.newFixedThreadPool OOM issue

ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < Integer.MAX_VALUE; i++) {
    executor.execute(() -> {
        try { Thread.sleep(10000); } catch (InterruptedException e) {}
    });
}

newFixedThreadPool uses an unbounded LinkedBlockingQueue, causing task accumulation and OOM.

14. Large file or bulk DB read OOM

List<String> lines = Files.readAllLines(path, charset); // loads whole file into memory

Use Files.line() or stream processing and close resources promptly.

15. Query‑then‑update concurrency issue

if (selectIsAvailable(ticketId)) {
    deleteTicketById(ticketId);
    // add cash
} else {
    return "No tickets";
}

Replace with atomic delete operation returning affected rows.

16. MySQL utf8 vs utf8mb4 for emojis

MySQL utf8 supports up to 3‑byte characters; emojis need 4 bytes. Use utf8mb4 charset.

17. Transaction not effective pitfalls

Database engine does not support transactions

Method not public

Incorrect rollbackFor Self‑invocation bypasses proxy

Exception swallowed by try/catch

Self‑invocation example shown; avoid by calling through Spring proxy.

18. Reflection overload pitfall

public class ReflectionTest {
    private void score(int score) { System.out.println("int grade =" + score); }
    private void score(Integer score) { System.out.println("Integer grade =" + score); }
    public static void main(String[] args) throws Exception {
        ReflectionTest t = new ReflectionTest();
        t.getClass().getDeclaredMethod("score", Integer.TYPE).invoke(t, Integer.valueOf("60")); // int version
        t.getClass().getDeclaredMethod("score", Integer.class).invoke(t, Integer.valueOf("60")); // Integer version
    }
}

19. MySQL timestamp auto‑update pitfall

CREATE TABLE t (
  a int,
  b timestamp NOT NULL,
  c timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

Both b and c may update to current time on row update. Use datetime or adjust explicit_defaults_for_timestamp.

20. MySQL 8 timezone pitfall

MySQL 8 defaults to UTC; set serverTimezone=Asia/Shanghai in JDBC URL to get correct local time.

jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaperformanceBackend Developmentcommon pitfalls
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.