How to convert String to int in Java - feature image

How to convert String to int in Java – peculiarities and pitfalls

In the previous article, I showed you that "" + i is the fastest way to convert an int into a String in Java. All the way from Java 7 to Java 14.

Today you will learn what to consider in the opposite direction, i.e., when parsing a String to an int. You can find the source code for this article in my GitLab repository.

Parsing decimal numbers

Let’s first look at the options to parse a String into an int (or Integer). Up to Java 7, we have these two methods:

  • int i = Integer.parseInt(s); (→ JavaDoc)
  • Integer i = Integer.valueOf(s); (→ JavaDoc)

The second method internally calls the first method and converts the result to an Integer object.

The String to convert must contain only digits, optionally with a plus or minus sign in front. These are allowed:

  • Integer.parseInt("47")
  • Integer.parseInt("+86400")
  • Integer.parseInt("-1")

The following Strings are not allowed and result in NumberFormatExceptions:

  • Integer.parseInt("") // Empty string not allowed
  • Integer.parseInt(" 1") // Space not allowed
  • Integer.parseInt("3.14") // Decimal point not allowed
  • Integer.parseInt("1,000") // Thousands separator not allowed

Parsing hexadecimal and binary numbers

The above methods parse decimal numbers. To parse other number systems, the following overloaded methods are available:

  • int i = Integer.parseInt(s, radix); (→ JavaDoc)
  • Integer i = Integer.valueOf(s, radix); (→ JavaDoc)

The parameter radix specifies the base of the number system. A hexadecimal number can be parsed as follows:

  • Integer.parseInt("CAFE", 16)

And a binary number like this:

  • Integer.parseInt("101111", 2)

Signed vs. unsigned ints

In all the above cases, the number to be parsed must be within the range Integer.MIN_VALUE (= -231 = -2,147,483,648) to Integer.MAX_VALUE (= 231-1 = 2,147,483,647).

It becomes interesting (not to say: confusing) if, for example, we convert the valid int value 0xCAFEBABE into a hex String and then back into an int:

int hex = 0xCAFEBABE;
String s = Integer.toHexString(hex);
int i = Integer.parseInt(s, 16);

This attempt results in the following error:

Exception in thread "main" java.lang.NumberFormatException: For input string: "cafebabe"

Why is that?

First of all: The String s contains “cafebabe” as expected. Why can’t this String be converted back to an int?

The reason is that the parseInt() method assumes the given number to be positive unless a minus sign precedes it. If you convert “cafebabe” to the decimal system, you get 3,405,691,582. This number is higher than Integer.MAX_INT and therefore, cannot be represented as an int.

Then why can we assign the number to the int variable hex? The binary representation of the numbers plays an (intended) trick on us. 0xCAFEBABE corresponds to binary 11001010,11111110,10111010,10111110 – a 32-digit binary number with the first bit being 1. In an int – which is always signed in Java – the first bit stands for the sign. If it is 1, the number is negative (for details on negative numbers, see this Wikipedia article). Let’s add some debug output to the code above:

int hex = 0xCAFEBABE;
System.out.println("hex        = " + hex);
System.out.println("hex binary = " + Integer.toBinaryString(hex));

String s = Integer.toHexString(hex);
System.out.println("s          = " + s);

int i = Integer.parseInt(s, 16);
System.out.println("i          = " + i);

We see that hex contains the negative value -889,275,714 (I’ve inserted the thousands separators here for the sake of clarity). Hexadecimally, this negative number is represented as the positive value “cafebabe”, which in turn cannot be converted back by the parseInt() method.

To make this work, after all, the language creators added the following methods to Java 8:

  • int i = Integer.parseUnsignedInt(s); (→ JavaDoc)
  • int i = Integer.parseUnsignedInt(s, radix); (→ JavaDoc)

These methods allow us to parse numbers in the range 0 to 4,294,967,295 (= 0xffffffff hexadecimal or 32 ones in the binary system). In Java 8, we can adjust the penultimate line of the above example as follows:

int i = Integer.parseUnsignedInt(s, 16);

As output, we don’t see 3,405,691,582. Rather, as the Java int is always signed, -889,275,714, which is the same value we get when we assign 0xCAFEBABE to an int.

And how do we get to 3,405,691,582? Therefore we have to parse “cafebabe” (or “CAFEBABE” – the case is insignificant) into a long:

long l = Long.parseLong(s, 16);

Finally, what does 3,405,691,582 look like in binary and hexadecimal notation?

System.out.println("l binary = " + Long.toBinaryString(l));
System.out.println("l hex    = " + Long.toHexString(l));

Again, we get the same representations as for the int value -889,275,714, i.e. 11001010,11111110,10111010,10111110 and “cafebabe”. The same binary or hexadecimal number thus leads – depending on whether it is stored in an int or in a long – to a different decimal number (if it is larger than Integer.MAX_VALUE). In the following section, we’ll take a look at some more examples.

parseInt() vs. parseUnsignedInt()

To illustrate the difference between parseInt() and parseUnsignedInt() once again, I wrote a small program, which you can find here in my GitLab repository, and which parses different (threshold) values using both methods.

In the following table, you find the result summarized (the dashes stand for NumberFormatExceptions):

StringBemerkungparseInt()Hexparse
Unsigned
Int()
Hex
-2147483649Integer.MIN_VALUE – 1
-2147483648Integer. MIN_VALUE-214748364880000000
-1000000000-1000000000c4653600
-1-1ffffffff
00000
100000000010000000003b9aca0010000000003b9aca00
2147483647Integer.MAX_VALUE21474836477fffffff21474836477fffffff
2147483648Integer.MAX_VALUE +1-214748364880000000
3000000000-1294967296b2d05e00
42949672952 * Integer.MAX_VALUE + 1-1ffffffff
42949672962 * Integer.MAX_VALUE + 2

One can see here well:

  • In the range 0 to Integer.MAX_VALUE, parseInt() and parseUnsignedInt() return the same results.
  • parseInt() also covers the range up to Integer.MIN_VALUE and returns exactly the value passed.
  • parseUnsignedInt() covers the range up to 2 * Integer.MAX_VALUE + 1 – with the result in the range above Integer.MAX_VALUE always being a negative number. Its hexadecimal representation, converted to the decimal system, corresponds to the input value.

Auto-boxing and -unboxing the result

We have seen above that there are separate methods to convert a String to an int primitive or an Integer object. But what happens if we use the wrong method?

  1. Integer i = Integer.parseInt("42");
  2. int i = Integer.valueOf("555");

The first case is not particularly elegant but does not pose a problem either: Integer.parseInt() works internally with primitive values and the result is – just as with Integer.valueOf() – eventually converted into an Integer object by auto-boxing.

The second case is different: here, the result is converted to an Integer object inside Integer.valueOf() and then back to an int primitive when it is assigned to i. IntelliJ recognizes this (Eclipse doesn’t) and displays a warning with the recommendation to replace valueOf() with parseInt():

Screenshot of the IntelliJ warning regarding redundant boxing
IntelliJ warning regarding redundant boxing

We will examine the extent to which the compiler or HotSpot forgives us for this error in the next chapter, “performance”.

Performance of the String-to-int conversion

Similar to the last article, I did the following comparison measurements with the Java Microbenchmark Harness – JMH:

  • Speed of various String-to-int conversion methods with Java 8:
    • parseInt() with positive numbers, positive numbers with preceding plus sign, and negative numbers,
    • parseUnsignedInt() with positive numbers and positive numbers with a preceding plus sign,
    • valueOf() with positive numbers,
    • parseInt() with subsequent conversion into an integer object,
    • valueOf() with subsequent conversion into an int primitive,
  • Comparison of the parseInt() method across all Java versions from Java 7 to Java 14.

Performance of various String-to-int conversion methods

You can find the source code of this test in my GitLab repository. The test results are in the results/ directory. The following table shows the performance of the various method calls using Java 8:

MethodOperations per secondConfidence interval (99,9%)
parseInt() positive value25,157,28924,959,166 – 25,355,412
parseInt() positive value with plus25,056,42724,974,885 – 25,137,970
parseInt() negative value25,143,74025,039,972 – 25,247,508
parseUnsignedInt() positive value25,124,02725,060,833 – 25,187,221
parseUnsignedInt() positive value with plus25,015,08224,914,320 – 25,115,843
parseInt() with subsequent boxing24,594,33624,421,316 – 24,767,355
valueOf() positive value24,531,18724,413,040 – 24,649,334
valueOf() with subsequent unboxing24,325,34724,183,155 – 24,467,538
Performance of the various String-to-int conversion methods using Java 8
Performance of the various String-to-int conversion methods using Java 8

As you can see, the first five measurements are almost identical. This can be explained quickly: the executed code is the same in all cases. valueOf() and parseInt() with subsequent boxing are about 2% slower. This should correspond to the overhead for converting into an Integer object. valueOf() with subsequent unboxing is about 1% slower, which means that neither the compiler nor HotSpot have forgiven us for the “boxing with subsequent unboxing” error.

Parsing negative numbers should be slightly faster because internally, negative numbers are added up, and in case of a positive number, the result is multiplied by -1. However, there are no differences in the benchmarks. Multiplying by -1 is apparently so fast that even at 25 million multiplications per second, this is of no significance.

Performance of String-to-int conversion across Java versions

Since in the end, all variants of the String-to-int conversion call Integer.parseInt(), I have restricted myself to measuring the performance of calling this particular method across different Java versions. I used the same test class as for the previous test and commented out all methods except integerParsePositiveInt(). I compiled and ran the code with the respective Java versions. You also find the results of these tests in the results/ folder. Here is a summary of the results:

Java version Operations per second Confidence interval (99.9%)
Java 725,223,11725,069,748 – 25,376,488
Java 825,157,28924,959,166 – 25,355,412
Java 922,580,11722,471,102 – 22,689,132
Java 1022,129,42521,889,153 – 22,369,698
Java 1123,657,22823,494,292 – 23,820,165
Java 1223,604,65723,385,208 – 23,824,106
Java 1323,626,04823,473,823 – 23,778,273
Java 1423,599,65823,440,825 – 23,758,490
Performance of the String-to-int conversion across Java versions
Performance of the String-to-int conversion across Java versions

Interestingly, the Integer.parseInt() method became significantly slower in Java 9 (nearly 10%), again 2% slower in Java 10 and faster again in Java 11, but since then has stayed about 5% behind the performance of Java 7 and 8. To confirm this measurement result, I ran all benchmark tests (which are repeated 25 times anyway) again – with similar results.

In search of the cause, I first compared the source codes of Integer.parseInt() of all Java versions. Versions 7 and 8 are identical. In Java 9, the code was slightly restructured, e.g., variables were declared elsewhere. The algorithm itself was not changed. The minimal code changes should not affect performance. From Java 9 to the Early Access Release of Java 14, there was no further change, except that in Java 12, the radix was included in the error message for non-parseable numbers.

To check whether the changes in Java 9 affected performance, I copied the Integer.parseInt() source codes from Java 8 and 9 and tested these copies with JMH. Both were the same speed. (This test is not in the GitLab repository because I don’t know to what extent I can publish Java source codes.)

In another experiment, I compiled the Integer.parseInt() source code with Java 8 and ran the resulting class file with Java 9 to 14. This led to a similar result as the initial performance test, i.e., Java 9 and 10 were slower, and Java 11 was a bit faster again. The reason for the different speeds must, therefore, lie within the JVM. If any of you know the exact cause, I would be happy to receive an enlightening comment.

Summary

In this article, I have shown how to parse numbers in decimal and other number systems and what the difference is between parseInt() and parseUnsignedInt(). Be careful not to box unnecessarily from int to Integer, or vice versa, or – worst of all – both in a row. If you find the article helpful, I’d be happy if you shared it with one of the following share buttons.

Leave a Comment

Your email address will not be published. Required fields are marked *