30 Practical Java Code Optimization Tips to Supercharge Your Applications

This article presents a comprehensive collection of thirty Java performance‑tuning techniques—from using String.format and buffered I/O streams to avoiding large transactions, leveraging enums, and handling thread‑local resources—each illustrated with clear code examples and best‑practice recommendations for modern backend development.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
30 Practical Java Code Optimization Tips to Supercharge Your Applications

Introduction

Hello everyone, I'm Su San, back with another Java performance‑tuning guide. After two popular posts on SQL and API optimization, we now dive into thirty practical Java code‑optimization tips.

1. Use String.format for String Concatenation

Instead of concatenating many parameters with the + operator, build the URL with String.format for better readability.

String requestUrl = "http://susan.sc.cn?userName=%s&age=%s&address=%s&sex=%s&roledId=%s";
String url = String.format(requestUrl, userName, age, address, sex, roledId);
Do not use String.format inside tight loops because it is slower than + or StringBuilder .

2. Create Buffered I/O Streams

Wrap raw FileInputStream and FileOutputStream with BufferedInputStream and BufferedOutputStream and use a byte buffer to reduce the number of read/write operations.

BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
    bos.write(buffer, 0, len);
}
bos.flush();

3. Reduce Loop Iterations

Replace nested loops with a map lookup to avoid O(N²) processing.

Map<Long, List<Role>> roleMap = roleList.stream()
    .collect(Collectors.groupingBy(Role::getId));
for (User user : userList) {
    List<Role> roles = roleMap.get(user.getRoleId());
    if (CollectionUtils.isNotEmpty(roles)) {
        user.setRoleName(roles.get(0).getName());
    }
}

4. Close Resources Promptly

Always close ResultSet, PreparedStatement, and Connection in a finally block or use try‑with‑resources.

try {
    // use resources
} finally {
    if (rs != null) rs.close();
    if (pstmt != null) pstmt.close();
    if (connection != null) connection.close();
}

5. Use Connection Pools

Replace raw JDBC connections with a pool such as Druid, C3P0, or Apache DBCP to reuse connections and limit the maximum number of active connections.

6. Cache Reflection Results

When using reflection for dynamic instantiation, cache the class instances or method look‑ups to avoid repeated costly reflective calls.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PayCode { String value(); String name(); }

@Service
public class PayService2 implements ApplicationListener<ContextRefreshedEvent> {
    private static Map<String, IPay> payMap = null;
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        Map<String, Object> beans = event.getApplicationContext()
            .getBeansWithAnnotation(PayCode.class);
        if (beans != null) {
            payMap = new HashMap<>();
            beans.forEach((k, v) -> {
                String bizType = v.getClass()
                    .getAnnotation(PayCode.class).value();
                payMap.put(bizType, (IPay) v);
            });
        }
    }
    public void pay(String code) { payMap.get(code).pay(); }
}

7. Use Thread Pools for Parallel Remote Calls

Replace sequential remote‑service calls with CompletableFuture (or Callable before Java 8) to execute them concurrently.

CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> getRemoteUser(id), executor);
CompletableFuture<Bonus> bonusFuture = CompletableFuture.supplyAsync(() -> getRemoteBonus(id), executor);
CompletableFuture<Growth> growthFuture = CompletableFuture.supplyAsync(() -> getRemoteGrowth(id), executor);
CompletableFuture.allOf(userFuture, bonusFuture, growthFuture).join();

8. Lazy Loading

Use lazy initialization (e.g., the lazy singleton pattern) to defer costly object creation until it is actually needed.

public class SimpleSingleton2 {
    private static SimpleSingleton2 INSTANCE;
    private SimpleSingleton2() {}
    public static SimpleSingleton2 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new SimpleSingleton2();
        }
        return INSTANCE;
    }
}

9. Pre‑size Collections

When you know the expected size, construct collections with an initial capacity to avoid repeated resizing.

List<Integer> list = new ArrayList<>(100000);

10. Avoid Large Transactions

Keep transactions short, move read‑only queries outside, avoid remote calls inside a transaction, and process data in batches.

11. Replace Long if…else Chains

Apply the Strategy pattern combined with a simple factory to map a code to a concrete implementation, eliminating massive if…else blocks.

public interface IPay { void pay(); }
@Service
public class AliaPay implements IPay {
    @PostConstruct
    public void init() { PayStrategyFactory.register("alia", this); }
    public void pay() { System.out.println("===Alipay==="); }
}
// similar for WeixinPay, JingDongPay
public class PayStrategyFactory {
    private static final Map<String, IPay> REGISTERS = new HashMap<>();
    public static void register(String code, IPay impl) { REGISTERS.put(code, impl); }
    public static IPay get(String code) { return REGISTERS.get(code); }
}
@Service
public class PayService3 {
    public void toPay(String code) { PayStrategyFactory.get(code).pay(); }
}

12. Prevent Infinite Loops

When using while(true), ensure the break condition is reliable; also guard recursive methods with a maximum depth to avoid stack overflow.

13. Beware of BigDecimal Precision

Never use the new BigDecimal(double) constructor; instead use BigDecimal.valueOf(double) or the String constructor to keep exact decimal values.

BigDecimal a = BigDecimal.valueOf(0.02);
BigDecimal b = BigDecimal.valueOf(0.03);
System.out.println(b.subtract(a)); // 0.01

14. Use Enums for Fixed Status Values

Replace scattered integer constants with a type‑safe enum that holds both code and description.

public enum OrderStatus {
    CREATE(1, "Created"),
    PAY(2, "Paid"),
    DONE(3, "Completed"),
    CANCEL(4, "Cancelled");
    private final int code;
    private final String message;
    OrderStatus(int code, String message) { this.code = code; this.message = message; }
    public int getCode() { return code; }
    public String getMessage() { return message; }
    public static OrderStatus fromCode(int code) {
        return Arrays.stream(values())
            .filter(s -> s.code == code)
            .findFirst()
            .orElse(null);
    }
}

15. Define Static Final Constants

Replace magic numbers and strings with private static final fields for readability and maintainability.

16. Avoid Large Transactions (Repeated)

Same advice as section 10 – keep transactions small, move selects out, avoid remote calls, and consider async processing.

17. Eliminate Overly Long if…else

Use design patterns (Strategy, Factory, Enum) to keep code clean and extensible.

18. Prevent Dead Loops in foreach

Do not remove elements from a collection while iterating with foreach; use an explicit iterator or a traditional for loop.

for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
    String s = it.next();
    if ("c".equals(s)) {
        it.remove();
    }
}

19. Avoid Unnecessary Logging

Guard expensive log statements with if (log.isDebugEnabled()) so they are only executed when the appropriate log level is active.

20. Write Constants First in equals()

When comparing strings, place the constant on the left side to avoid NullPointerException.

if ("SuSan".equals(user.getName())) { ... }

21. Meaningful Naming

Use clear, English, camel‑case names for variables, methods, and classes; avoid single letters, Chinese pinyin, or inconsistent styles.

22. SimpleDateFormat Is Not Thread‑Safe

Never share a static SimpleDateFormat instance; either create a new instance per use, store it in a ThreadLocal, or switch to Java 8's DateTimeFormatter.

23. Prefer Custom ThreadPoolExecutor Over Executors

Define explicit core size, max size, keep‑alive time, queue capacity, and rejection policy to avoid unbounded queues or thread explosion.

ExecutorService pool = new ThreadPoolExecutor(
    8, 10, 60, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(500),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

24. Arrays.asList Returns Fixed‑Size List

The list returned by Arrays.asList cannot be resized; use new ArrayList<>(Arrays.asList(...)) if you need to add or remove elements.

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
list.add("d");

Conclusion

If you found these tips useful, please like, follow, or star the GitHub repository https://github.com/dvsusan/susanSayJava for more Java performance articles.

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.

Backendjava
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

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.