javaexceptionjava-streamnumberformatexception

How to handle NumberFormatException with Java StreamAPI


Is there a way to filter out all values that are bigger than the max value that can be stored in a Long using Stream API?

The current situation is that you can search in the frontend with a simple search bar after some customers by using their ID.

For example: 123456789, 10987654321. If you put a "separator" between these two IDs, everything works. But if you forget the "separator" my code is trying to parse 12345678910987654321 into a Long and I guess there is the problem.

That causes a NumberFormatException after trying to search. Is there a way to filter these numbers out that can't be parsed into a Long because they are too big?

String hyphen = "-";

String[] customerIds = bulkCustomerIdProperty.getValue()
              .replaceAll("[^0-9]", hyphen)
              .split(hyphen);
...
customerFilter.setCustomerIds(Arrays.asList(customerIds).stream()
              .filter(n -> !n.isEmpty()) 
              .map(n -> Long.valueOf(n)) // convert to Long
              .collect(Collectors.toSet()));

Solution

  • You can either extract parsing into a separate method and wrap it with a try/catch (which also gives a possibility to log invalid data, instead of ignoring it), or use BigInteger to eliminate values that exceed the range of long.

    Example with BigInteger:

    Set<Long> result =  Stream.of("", "12345", "9999999999999999999999999999")
            .filter(n -> !n.isEmpty())
            .map(BigInteger::new)
            .filter(n -> n.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0 &&
                         n.compareTo(BigInteger.valueOf(Long.MIN_VALUE)) >= 0)
            .map(BigInteger::longValueExact) // convert to Long
            .peek(System.out::println) // printing the output
            .collect(Collectors.toSet());
    

    Example with handling NumberFormatException in a separate method:

    Set<Long> result =  Stream.of("", "12345", "9999999999999999999999999999")
            .filter(n -> !n.isEmpty())
            .map(<ClassName>::safeParse)
            .flatMap(OptionalLong::stream) // extracting long primitive and boxing it into Long
            .peek(System.out::println)     // printing the output
            .collect(Collectors.toSet());
    
    public static OptionalLong safeParse(String candidate) {
        try {
            return OptionalLong.of(Long.parseLong(candidate));
        } catch (NumberFormatException e) {
            // logger.error("failed to parse: " + candidate, e); // logging an invalid piece of data
            return OptionalLong.empty();
        }
    }
    

    Output (from peek())

    12345
    

    Note: method OptionalLong.stream() used above comes with the JDK 9. With Java 8 you can use a combination of filter(OptionalLong::isPresent).map(OptionalLong::getAsLong) instead.