Most developers take it for granted that performance tuning is complex and requires a lot of experience and knowledge. Well, that’s not entirely wrong. Optimizing an application for optimal performance is not an easy task. But that doesn’t mean you can’t do anything if you don’t have the knowledge.

Here are 11 easy-to-follow tips and best practices to help you create a well-performing application.

Most of the recommendations are Java specific. But there are several language-neutral recommendations that can be applied to all applications and programming languages. Before discussing java-specific performance tuning tips, let’s look at general tips.

1. Don’t optimize until you know it’s necessary

This is probably one of the most important performance tuning techniques. You should follow common best practices and try to implement use cases efficiently. However, this does not mean that you should replace any standard libraries or build complex optimizations before you prove necessary.

In most cases, premature optimization not only takes a lot of time, but also makes the code difficult to read and maintain. Worse, these optimizations usually don’t do any good, because you’re spending a lot of time optimizing non-critical parts of your application.

So, how do you justify that you need to optimize something?

First, you need to define how fast your application code can go, for example, by specifying a maximum response time for all API calls, or by specifying the number of records to import within a specific time range. Once you’ve done that, you can measure which parts of your application are too slow to improve. Then, move on to the second technique.

2. Use profilers to find the real bottleneck

After you’ve followed the first tip and identified some parts of your application that need improvement, where do you start?

You can solve the problem in two ways:

  • Look at your code and start with the parts that look suspicious or that you think might cause problems.

  • Or use a profiler and get detailed information about the behavior and performance of each part of the code.

I hope I don’t need to explain why the second approach should always be followed.

Obviously, a profiler based approach gives you a better understanding of the performance impact of your code and allows you to focus on the most critical parts. If you’ve ever used a profiler, you remember how surprised you were to immediately figure out which parts of your code were causing performance problems. To be honest, my first guess led me astray more than once.

3. Create a performance test suite for the entire application

This is another general tip that can help you avoid many of the unexpected problems that often occur after deploying performance improvements into production. You should always define a performance test suite that tests the entire application and runs it before and after performance improvements.

These additional test runs will help you identify the functional and performance side effects of the changes and ensure that you don’t result in updates that do more harm than good. This is especially important if you are working on components that are used by several different parts of your application, such as databases or caches.

4. Deal with the biggest bottlenecks first

After you have created the test suite and profiled the application with a profiler, you can make a list of issues that need to be addressed to improve performance. That’s good, but it still doesn’t answer the question of where you should start. You can focus on quick fixes or start with the most important problems. You have to know that, too.

Quick fixes can be attractive at first because you can show the first results quickly. But sometimes, you may need to convince other team members or management that performance analysis is worth it — because it doesn’t pay off at the moment.

But in general, I recommend dealing with the most important performance issues first. This will give you the greatest performance improvement, and you may no longer have to solve some of these problems to meet performance requirements.

This is the end of common performance tuning techniques. Let’s take a closer look at some Java-specific techniques.

5. Use StringBuilder to programmatically connect strings

There are many different options for concatenating Strings in Java. For example, you can use simple + or + =, as well as StringBuffer or StringBuilder.

So which approach should you take?

The answer depends on the code that concatenates the String. If you are adding new content to a String programmatically, such as in a for loop, then you should use StringBuilder. It is easy to use and provides better performance than StringBuffer. But remember that, in contrast to StringBuffer, StringBuilder is not thread-safe and may not be suitable for all use cases. You have to know that.

You simply instantiate a new StringBuilder and call the Append method to add a new part to the String. After you have added all the parts, you can call the toString() method to retrieve the String of the join.

The code snippet below shows a simple example. During each iteration, the loop converts I to a String and adds it to StringBuilder SB along with a space. So, finally, This code will write “This is a test0 1 2 3 4 5 6 7 8 9” to the log file.

StringBuilder sb = new StringBuilder(" This is atest");for(int i=0; i<10; i++) { sb.append(i); Sb. Append (" "); } log.info(sb.toString());Copy the code

As you can see in the code snippet, you can supply the first element of a String to the constructor. This creates a new StringBuilder that contains the supplied String and the capacity for 16 additional characters. As you add more characters to StringBuilder, the JVM dynamically increases the StringBuilder’s size.

If you already know how many characters your String will contain, you can supply that number to different constructors to instantiate a StringBuilder with defined capacity. This further improves efficiency because it does not need to dynamically expand its capacity.

6. Use + to concatenate a String in a statement

When you implemented your first application in Java, you were probably told that you shouldn’t use + to concatenate strings. This is true if you are concatenating strings in your application logic. Strings are immutable, and the concatenation result of each String is stored in a new String object. This requires extra memory and can slow down your application, especially if you concatenate multiple strings in a loop.

In these cases, you should follow tip 5 and use StringBuilder.

However, if you just split the string into multiple lines to improve the readability of the code, the situation is different.

Query q = em.createQuery(“SELECT a.id, a.firstName, a.lastName ”
+ “FROM Author a ”
+ “WHERE a.id = :id”);
Copy the code

In these cases, you should concatenate your strings with a simple +. The Java compiler optimizes this and performs the join at compile time. So, at runtime, your code will only use 1 String, no concatenation required.

7. Use primitives whenever possible

Another easy and quick way to avoid any overhead and improve application performance is to use primitive types instead of their wrapper classes. Therefore, it is best to use int instead of Integer and double instead of double. This allows the JVM to store values on the stack instead of the heap to reduce memory consumption and to do more efficient processing.

8. Try to avoid BigInteger and BigDecimal

While we’re talking about data types, let’s also take a quick look at BigInteger and BigDecimal. The latter, in particular, is popular for its accuracy. But there is a price.

BigInteger and BigDecimal require more memory than simple long or double, and can significantly slow down all calculations. So, if you need extra precision, or if the numbers are going to be out of long’s range, think twice. This may be the only way you need to change to solve a performance problem, especially when implementing a mathematical algorithm. I want you to understand that.

9. Check the current log level

This advice should be obvious, but unfortunately, many programmers mostly ignore it when writing code. Before you create debug messages, always check the current log level first. Otherwise, you might create a log message string that will be ignored later.

Here are two examples of the opposite.

/ / don 'tdoThis log.debug(" User [" + userName + "] called method X with [" + I + "] "); // Or this log.debug(string. format(" User [%s] called method X with [%d] ", userName, I));Copy the code

In both cases, you will perform all the necessary steps to create log messages without knowing whether the logging framework will use them. Therefore, it is a good idea to check the current log level before creating debug messages.

// do this
if(log.isdebugenabled ()) {log.debug(" User [" + userName + "] called method X with [" + I + "] "); }Copy the code

10. Use Apache Commons stringutils. Replace instead of String.replace

In general, the String.replace method works fine and is efficient, especially with Java 9. However, if your application requires a lot of replacement and is not updated to the latest Java version, it is still worth looking for faster and more efficient alternatives.

One alternative is the Apache Commons Lang’s Stringutils.replace method. As Lukas Eder described in a recent blog post, the Stringutils.replace method is far superior to Java 8’s String.replace method.

And it requires only minor changes. Add the Maven dependencies of the Apache Commons Lang project to the application pop.xml and replace all calls to the Stringutils.replace method with the Stringutils.replace method.

/ / replace this test. The replace ("test", "simpletest"); // with this StringUtils.replace(test."test", "simpletest");Copy the code

11. Cache expensive resources, such as database connections

Caching is a popular solution to avoid repeated execution of expensive or frequently used snippets of code. The general idea is simple: it’s cheaper to reuse these resources than to repeatedly create new ones.

A typical example is a database connection in a cache pool. New connections take time to create, and you can avoid this if you reuse existing connections.

You can also find other examples in the Java language itself. For example, the valueOf method of the Integer class caches values between -128 and 127. You could argue that creating a new Integer isn’t too expensive, but because it’s used so often, caching the most commonly used values can also provide performance benefits.

However, when you consider caching, keep in mind that caching implementations also incur overhead. You need to spend extra memory to store reusable resources, so you may need to manage caches to make resources accessible, as well as remove outdated resources.

So, before you start caching any resources, make sure it’s worthwhile to implement caching, which means you have to use them in sufficient quantities.

Summary As you can see, sometimes it doesn’t take much work to improve the performance of an application. Most of the suggestions in this article require only a little effort on your part to apply to your code.

But the most important tips are those that don’t matter what programming language they are:

  • Don’t optimize until you know you need to
  • Use profilers to find the real bottleneck
  • Deal with the biggest bottlenecks first