Java wrapper types— tips to avoid performance pitfalls

Paweł Świderski
5 min readOct 5, 2021

Long, Integer, Double, Float, Short, Boolean, Character, Byte are wrapper versions of primitive ones — long, int, double, float, short, boolean, char, byte. Wrappers allow to keep values in the collections like lists, sets, maps. Additional advantage of wrapper classes over primitive ones is possibility of method execution on those object.

For example, we can do:

counter.toString();

or

counterB.compareTo(counterA);

on e.g. Long, Integer, Double or Float object.

Wrapper type is kept in memory on the heap and stack holds the reference to the object on the heap. Primitive values are located only on the stack.

Arithmetical operations on wrapper vs primitive

Let’s analyze this

public static void main(String[] args) {
int repeats = 40000000;
long time;

time = System.currentTimeMillis();
long counterA = 0L;
for (int i = 0; i < repeats; i++) {
counterA = counterA + 4L;
}
System.out.println(counterA + " A: " + (System.currentTimeMillis() - time) + " ms");

time = System.currentTimeMillis();
Long counterB = 0L;
for (int i = 0; i < repeats; i++) {
counterB = counterB + 4L;
}
System.out.println(counterB + " B: " + (System.currentTimeMillis() - time) + " ms");
}

The same summing up is done on primitive longs and wrapper Longs.

Same task but the difference in processing time is huge. Longs are summed up much longer:

Average time of summing up numbers based on primitives and wrapper types in case of longs.

The reason for that is unnecessary allocation of objects. Each addition produces new Long object. There are lots of objects that has to be garbage collected later on.

Wrapper types are not needed for calculation.

Do you need wrapper or primitve — use proper conversion method parseType/valueOf

There is a significant difference between methods that parse string to the primitive number like:

  • Long.parseLong
  • Integer.parseInt
  • Double.parseDouble
  • Float.parseFloat

and methods that return wrapper type of that number:

  • Long.valueOf
  • Integer.valueOf
  • Double.valueOf
  • Float.valueOf

It is a very often practise to use valueOf for getting a primitive which is wrong.

You may even do this unconsciously which can be costly when used in code that is often run.

I have done a test that compares time of processing Long.parseLong and Long.valueOf for different numbers many times — 20000 in the test.

This is a code:

public static void main(String[] args) {
int repeats = Integer.parseInt(args[0]);

List<String> numbers = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
numbers.add(String.valueOf(i));
}

long time;

time = System.currentTimeMillis();
long sumOfLongsA = 0L;
for (int i = 0; i < repeats; i++) {
for (String number : numbers) {
sumOfLongsA += Long.parseLong(number);
}
}
System.out.println(sumOfLongsA + " Parse: " + (System.currentTimeMillis() - time) + " ms");

time = System.currentTimeMillis();
long sumOfLongsB = 0L;
for (int i = 0; i < repeats; i++) {
for (String number : numbers) {
sumOfLongsB += Long.valueOf(number);
}
}
System.out.println(sumOfLongsB + " value: " + (System.currentTimeMillis() - time) + " ms");
}

Result looks as follows:

Average time of summing up numbers with usage of Long.valueOf and Long.parseLong

As you see getting wrapper type of long makes the whole processing 80% slower.

The same test was repeated for integers, doubles and floats.

Average time of summing up numbers with usage of Integer.valueOf and Integer.parseInt
Average time of summing up numbers with usage of Double.valueOf and Double.parseDouble
Average time of summing up numbers with usage of Float.valueOf and Float.parseFloat

As you see when it comes to decimal numbers there is smaller difference between summing up wrapper type and primitive numbers but still wrapper types are 30% slower.

Unboxing and autoboxing

Unboxing and autoboxing make the problem described above even more hidden.

public static void main(String[] args) {
int repeats = Integer.parseInt(args[0]);

List<String> numbers = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
numbers.add(String.valueOf(i));
}

long time;

time = System.currentTimeMillis();
float sumOfLongsB = 0;
for (int i = 0; i < repeats; i++) {
for (String number : numbers) {
sumOfLongsB += calculateValueObject(number);
}
}
System.out.println(sumOfLongsB + " value: " + (System.currentTimeMillis() - time) + " ms");

time = System.currentTimeMillis();
double sumOfLongsA = 0;
for (int i = 0; i < repeats; i++) {
for (String number : numbers) {
sumOfLongsA += calculateValuePrimitive(number);
}
}
System.out.println(sumOfLongsA + " Parse: " + (System.currentTimeMillis() - time) + " ms");


}

private static float calculateValuePrimitive(String number) {
final int baseCalculation = Integer.parseInt(number);
return baseCalculation * 1.23f;
}

private static float calculateValueObject(String number) {
final int baseCalculation = Integer.valueOf(number);
return baseCalculation * 1.23f;
}

Let’s analyze the same case but with calculation methods. Each calculation method does the same. The difference is that in calculateValueObject Integer.valueOf was used instead of Integer.parseInt.

As you see further calculations are done on the primitive int so wrapper integer is only available for a moment in:

final int baseCalculation = Integer.valueOf(number);

where it is unboxed to primitive int.

That error can be costly:

Average time of summing up numbers with usage of Integer.valueOf and Integer.parseInt in calculation methods.

The same issue can take place when you pass primive to the method that requires wrapper type. E.g.:

private static float calculateValuePrimitive(String number) {
final int baseCalculation = Integer.parseInt(number);

return calculateWithVatTax(baseCalculation);
}

private static float calculateWithVatTax(Integer baseCalculation) {
return baseCalculation * 1.23f;
}

private static float calculateValueObject(String number) {
final int baseCalculation = Integer.valueOf(number);
return baseCalculation * 1.23f;
}

The autoboxing can make a significant impact on the performance:

Average time of summing up numbers with usage of Integer.parseInt with unnecessary autobixing and Integer.parseInt without it in calculation methods.

In this case autoboxing made it 35% slower.

Summary

Do not use wrapper types when you do not need to! Wrapper types are crucial when it comes to keeping data. For computational use primitives only!

--

--