# Best Practices in Java

I'd like to share some best practices to help you effectively use the Java programming language and its fundamental libraries.

This article has 4 parts:

* **General programming**: how to use variables, control structures, libraries, data types,... effectively.
    
* **Lambdas and Streams**: how to make the best use of functional interfaces, lambdas, and method references.
    
* **Exceptions**: guidelines for using exceptions effectively.
    
* **Concurrency**: write clear and correct concurrent programs.
    

Ok, let's go!!!

# I. General Programming

### a. Prefer *for-each* loops to traditional *for loops*

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679114068864/dd0a62f8-48a8-40bb-8382-49eca3e581dc.png align="center")

**for-each loops** get rid of the clutter and the opportunity for error by hiding the iterator or index variable. There is no performance penalty for using for-each

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679114095797/c2cca0c9-700b-4eea-bf87-a74594dfa5ab.png align="center")

However, there are some common situations where you can’t use for-each:

* need the array index in order to do something
    
* need to traverse multiple collections in parallel, then you need explicit control over the iterator or index variable
    

### b. Prefer primitive types to boxed primitives

* Primitives are more time and space efficient than boxed primitives
    
* Applying the == operator to boxed primitives is almost always **wrong** =&gt; be careful when comparing boxed primitives.
    
* When you mix **primitives** and **boxed primitives** in an operation, the boxed primitive is **auto-unboxed.** If a null object reference is auto-unboxed, you get a NullPointerException
    
* Repeatedly boxed and unboxed causing performance degradation.
    

### c. Avoid Strings where other types are more appropriate

* If it’s **numeric**, it should be translated into the appropriate numeric type, such as **int, long**, **float**, or **BigInteger**
    
* If it’s the answer to a **yes-or-no** question, it should be translated into an appropriate **enum** type or a **boolean**
    

### d. Beware the performance of string concatenation

Strings are ***immutable,*** so when two strings are concatenated, the contents of both are copied and Java creates a new String

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679116493445/f59ebfa1-84e9-48db-ac7f-1023fa7596d4.png align="center")

To achieve acceptable performance, use a **StringBuilder** in place of a **String**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679116536442/aed170ac-e234-470c-ba81-10ec8da49500.png align="center")

### e. Refer to objects by their interfaces

If appropriate interface types exist, then **parameters**, **return** values, **variables**, and **fields** should all be declared using interface types

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679124883963/5af20ed8-d617-4f32-b2df-ba27227d34eb.png align="center")

Your program will be much more flexible to switch implementations. However, it is entirely appropriate to refer to an object by a class rather than an interface if no appropriate interface exists. If there is no appropriate interface, just use the least specific class in the class hierarchy that provides the required functionality\*\*.\*\*

# II. Lambdas and Streams

### a. Prefer lambdas to anonymous classes

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679125176389/9f1b3247-f20e-4b75-b131-0248e9bb148a.png align="center")

In Java 8, the language formalized the notion that interfaces with a single abstract method are special and deserve special treatment. These interfaces are now known as ***functional interfaces***, and the language allows you to ***create instances of these interfaces using lambda expressions***, or lambdas for short.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679125314668/668790a6-6cc1-4d5f-8055-4f2deed4ffe4.png align="center")

However, lambdas lack names and documentation. If a computation isn’t self-explanatory or exceeds a few lines, don’t put it in a lambda. **One line** is ideal for lambda, and **three lines** are a reasonable maximum.

### b. Prefer method references to lambdas

Java provides a way to generate function objects even more succinct than lambdas: ***method references***

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679126388443/2467d3cd-0529-456f-a087-5846792f7590.png align="center")

should become:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679126404826/505abe3b-38a9-4371-b57b-287d5f8fcefb.png align="center")

However, where method references are shorter and clearer, use them; where they aren’t, stick with lambdas.

### c. Favor the use of standard functional interfaces

If one of the standard functional interfaces does the job, you should generally use it in preference to a purpose-built functional interface.

Of course you need to write your own if none of the standard ones does what you need, for example if you require a predicate that takes three parameters.

### d. Use streams judiciously

* Overusing streams makes programs hard to read and maintain.
    
* In the absence of explicit types, careful naming of lambda parameters is essential to the readability of stream pipelines.
    
* Using helper methods is important for readability in stream pipelines.
    

### f. Prefer Collection to Stream as a return type

When writing a method that returns a sequence of elements, remember that some of your users may want to process them as a **stream** while others may want to **iterate** over them

\=&gt; Collection or an appropriate subtype is generally the best return type

### g. Use caution when making streams parallel

Java 8 introduced streams, which can be parallelized with a single call to the **parallel** method.

* Parallelizing a pipeline is unlikely to increase its performance if the source is from **Stream.iterate**, or the intermediate operation **limit** is used
    
* Performance gains from parallelism are best on streams over **ArrayList**, **HashMap**, **HashSet**, and **ConcurrentHashMap** instances; **arrays**; **int** **ranges**; and **long ranges.** What these data structures have in common is that they can all be accurately and cheaply split into subranges of any desired sizes, which makes it easy to divide work among parallel threads
    
* Do not even attempt to parallelize a stream pipeline unless you have good reason to believe that it will preserve the correctness of the computation and increase its speed
    

# III. Exceptions

### a. Use checked exceptions for recoverable conditions and runtime exceptions for programming errors

* Use checked exceptions for conditions from which the caller can reasonably be expected to recover.
    
    *Ex: For example, suppose a checked exception is thrown when an attempt to make a purchase with a gift card fails due to insufficient funds. The exception should provide an accessor method to query the amount of the shortfall. This will enable the caller to relay the amount to the shopper.*
    
* If a program throws an unchecked exception or an error, it is generally the case that recovery is impossible and continued execution would do more harm than good.
    
    *Ex:* ***ArrayIndexOutOfBoundsException, NullPointerException***
    
* If it isn’t clear whether recovery is possible, you’re probably better off using an unchecked exception
    

### b. Avoid unnecessary use of checked exceptions

* Overuse of checked exceptions places a burden on the user of the API
    
* If callers won’t be able to recover from failures, throw unchecked exceptions. If recovery may be possible and you want to force callers to handle exceptional conditions, first consider returning an **optional**.
    

However, if **optional** provides insufficient information in the case of failure, you should throw a checked exception.

### c. Favor the use of standard exceptions

The Java libraries provide a set of exceptions that cover most of the exception-throwing needs of most APIs.

This table summarizes the most commonly reused exceptions:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679156766293/1c6ee740-dee8-4e2c-8f81-23f616f0f842.png align="center")

Do not reuse **Exception**, **RuntimeException**, **Throwable**, or **Error** directly.

### d. Throw exceptions appropriate to the abstraction

Higher layers should catch *lower-level* exceptions and, in their place, throw exceptions that can be explained in terms of the *higher-level* abstraction ***(exception translation)***

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679156897223/286af9fd-7dfe-4a61-9071-ff5c9920fd61.png align="center")

### e. Include failure-capture information in detail messages

* To capture a failure, the detail message of an exception should contain the values of all parameters and fields that contributed to the exception.
    

*Ex: The detail message of an* ***IndexOutOfBoundsException*** *should contain the* ***lower bound***\*, the\* ***upper bound***\*, and the\* ***index*** ***value that failed*** *to lie between the bounds*

* Do not include **passwords**, **encryption keys**,... in detail messages
    

### f. Strive for failure atomicity

A failed method invocation should leave the object in the state that it was in prior to the invocation. There are several ways to achieve this effect:

* Design **immutable** objects
    
* For methods that operate on **mutable** objects ==&gt; **check parameters for validity** **before performing** the operation (exceptions will be thrown before object modification commences)
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679157729351/f5273650-7517-416d-8135-f5a69e0701ef.png align="center")
    

### g. Don't ignore exceptions

When the designers of an API declare a method to throw an exception, they are trying to tell you something. Don’t ignore it!

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679192847562/c62d9d56-cefe-4fe3-99b7-435eedc785ec.png align="center")

If you choose to ignore an exception, the catch block should contain a **comment** explaining why it is appropriate to do so, and the **variable should be named ignored**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679192925292/88f842fd-c996-4708-921b-4f6d67fe15d2.png align="center")

# IV. Concurrency

### a. Synchronize access to shared mutable data

The synchronized keyword ensures that only a single thread can execute a method or block at one time.

* **when multiple threads share mutable data, each thread that reads or writes the data must perform synchronization**
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679194964025/47f4e8ae-87d1-401c-8c22-46572cad0a49.png align="center")
    
    *without synchronization, there is no guarantee that one thread’s changes will be visible to another thread*
    
* ***volatile*** modifier performs *no mutual exclusion*, but guarantees that any thread that reads the field will see the ***most recently*** written value
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679194986373/5d6c403f-2a60-4a39-b5a0-ecbe8aa05bd4.png align="center")
    

However, you have to be careful when using **volatile.** For example, in the following method, the problem is that the increment operator (**++**) is not atomic. It performs ***two operations*** (volatile still works if performs one operation) on the ***nextSerialNumber*** field: first it reads the value, and then it writes back a new value:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679194341384/d6b83604-ff52-44fe-983d-4aec32304bdc.png align="center")

If a second thread reads the field between the time a thread reads the old value and writes back a new one, the second thread will see the same value as the first and return the same serial number ==&gt; **computes the wrong results**.

One way to fix it is to use **AtomicLong**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679194623659/dff71028-51d6-4d1c-b654-7f7a2c4af5ae.png align="center")

The best way to avoid the problems discussed is not to share mutable data.

### b. Avoid excessive synchronization

* Excessive synchronization can cause reduced performance, deadlock,...
    
* Inside a synchronized region, do not invoke a method that is designed to be overridden, or one provided by a client in the form of a function object
    
    \=&gt; has no knowledge of what the method does and has no control over it.
    

There are some ways to **move the alien method invocations out of the synchronized block**:

* taking a *“snapshot”* of shared mutable data =&gt; safely traversed without a lock
    
* use ***concurrent collections***\*: CopyOnWriteArrayList\*, *ConcurrentHashMap*,...
    

**As a rule, you should do as little work as possible inside synchronized regions!** If you must perform some time-consuming activity, find a way to move it out of the synchronized region.

If you are writing a mutable class, you have two options: you can omit all synchronization and **allow the client to synchronize externally** if concurrent use is desired, or you can **synchronize internally**, making the class thread-safe

### c. Prefer executors, tasks, and streams to threads

Instead of creating and managing threads manually, you can create a *thread pool* with a fixed or variable number of threads. I prefer **ThreadPoolExecutor** or **ThreadPoolTaskExecutor** (in SpringBoot) for a heavily loaded production server.

### d. Prefer concurrency utilities to *wait* and *notify*

Instead of using *wait* and *notify*, you should use the higher-level concurrency utilities in ***java.util.concurrent*** package: *Executor*, *concurrent collections* and *synchronizers.*

* use **ConcurrentHashMap** in preference to **Collections.synchronizedMap**
    
* For interval timing, use **System.nanoTime** rather than **System.currentTimeMillis**
    
    (more accurate and more precise and is unaffected by adjustments to the system’s real-time clock)
    

If you have to maintain legacy code that uses *wait* and *notify,* **always use the *wait* loop idiom (inside a synchronized region) to invoke the *wait* method; never invoke it outside of a loop:**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679198057835/ca7cbf7e-a138-4d68-a2b0-41844f3a78f8.png align="center")

### e. Use lazy initialization judiciously

The best advice for lazy initialization is *“don’t do it unless you need to”.* It decreases the cost of initializing a class or creating an instance, at the expense of increasing the cost of accessing the lazily initialized field.

**Under most circumstances, normal initialization is preferable to lazy initialization.**

If you must initialize a field lazily in order to achieve your performance goals or break a harmful initialization circularity:

* For instance fields, use *double-check idiom (singleton pattern)*
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679200304576/e5e1971a-6acf-4f3f-ae2a-47366bcf2c15.png align="center")
    
* For static fields, use *lazy initialization holder class idiom*
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679200279727/e0f3dcad-986a-4e8d-8e93-21d9c087ebc3.png align="center")
    

### f. Don't depend on the thread scheduler

When many threads are runnable, the thread scheduler determines which ones get to run and for how long. Any reasonable operating system will try to make this determination fairly, but the policy can vary. Therefore, **well-written programs shouldn’t depend on the details of this policy.**

The best way to write a robust, responsive, portable program is to ensure that the average number of runnable threads is not significantly greater than the number of processors. This leaves the thread scheduler with little choice: it simply runs the runnable threads till they’re no longer runnable. **Note that the number of runnable threads isn’t the same as the total number of threads, which can be much higher. Threads that are *waiting are not runnable.***

In terms of the Executor Framework, this means **sizing thread pools appropriately** and keeping tasks short, but not *too* short, or dispatching overhead will harm performance.

---

Phewww!!! I've just shared with you guys some important tips and tricks to work with Java efficiently. If you find it helpful, please share it with everyone to join hands in building a stronger tech community in Vietnam.

# References

1. [**Effective Java** 3rd Edition](https://www.amazon.com/Effective-Java-Joshua-Bloch/dp/0134685997)
