Java Exception Handling – InputMismatchException

Moving along through our in-depth Java Exception Handling series, today we’ll be examining the InputMismatchException. The InputMismatchException is thrown when attempting to retrieve a token using the text Scanner class that doesn’t match the expected pattern or type.

In this article we’ll explore the InputMismatchException in more detail by first looking at where it sits in the larger Java Exception Hierarchy. We’ll also explore the basic purpose and usage of the built-in Scanner class, and see how improper use of this class can result in unintended InputMismatchExceptions in your own code, so let’s dig in!

The Technical Rundown

All Java errors implement the java.lang.Throwable interface, or are extended from another inherited class therein. The full exception hierarchy of this error is:

Full Code Sample

Below is the full code sample we’ll be using in this article. It can be copied and pasted if you’d like to play with the code yourself and see how everything works.

package io.airbrake; import io.airbrake.utility.Logging; import java.io.File; import java.util.InputMismatchException; import java.util.List; import java.util.Scanner; import java.util.regex.MatchResult; import java.util.stream.Collectors; import java.util.stream.Stream; public class Main { private static final String FILE_PATH = "2014_world_gdp_with_codes.csv"; // Pads trio of values by 35 spaces, with explicit alignment of floats on decimal point. private static final String OUTPUT_FORMAT = "%-35s%10.2f%25s"; // Matches CSVs, with first value including optional quotations, spaces, etc. private static final String REGEX_PATTERN = "("?[-()',\w\s]+"?),(\d*\.?\d*?),(\w+)n"; public static void main(String[] args) { Logging.lineSeparator("DELIMITER TEST, BY TYPES", 70); delimiterTestByTypes(); Logging.lineSeparator("DELIMITER TEST, WITH INVALID TYPES", 70); delimiterTestByDirectTypes(); Logging.lineSeparator("RESULT STREAM TEST", 70); resultStreamTest(); } /** * Retrieves and outputs Scanner results using Java 9 findAll() pattern matching method. */ private static void resultStreamTest() { try { // Create Scanner to parse passed file. Scanner scanner = new Scanner(new File(FILE_PATH)); // Find all regex matches from REGEX_PATTERN. Stream<MatchResult> resultStream = scanner.findAll(REGEX_PATTERN); // Use ResultStream to collect results into a list. List<MatchResult> list = resultStream.collect(Collectors.toList()); // Iterate MatchResults to extract and output values. for (MatchResult result : list) { String country = result.group(1); Double gdp = Double.valueOf(result.group(2)); String code = result.group(3); // Output values using OUTPUT_FORMAT. Logging.log(String.format(OUTPUT_FORMAT, country, gdp, code)); } // Close scanner after completion. scanner.close(); } catch (InputMismatchException exception) { // Output unexpected InputMismatchExceptions. Logging.log(exception); } catch (Exception exception) { // Output unexpected Exceptions. Logging.log(exception, false); } } /** * Retrieves and outputs Scanner results using Scanner delimiter and getScannerValueByType() results. */ private static void delimiterTestByTypes() { try { // Create Scanner to parse passed file, using either comma- or newline-delimiter. Scanner scanner = new Scanner(new File(FILE_PATH)).useDelimiter("[,\n]"); // Iterate through new lines when scanner has a next value. while (scanner.hasNextLine() && scanner.hasNext()) { // Get next values indirectly through getScannerValueByType() method. Object country = getScannerValueByType(String.class, scanner); Double gdp = (Double) getScannerValueByType(Double.class, scanner); Object code = getScannerValueByType(String.class, scanner); // Output values using OUTPUT_FORMAT. Logging.log(String.format(OUTPUT_FORMAT, country, gdp, code)); } // Close scanner after completion. scanner.close(); } catch (InputMismatchException exception) { // Output unexpected InputMismatchExceptions. Logging.log(exception); } catch (Exception exception) { // Output unexpected Exceptions. Logging.log(exception, false); } } /** * Retrieves and outputs Scanner results using Scanner delimiter and direct next[TYPE] type method calls. */ private static void delimiterTestByDirectTypes() { try { // Create Scanner to parse passed file, using either comma- or newline-delimiter. Scanner scanner = new Scanner(new File(FILE_PATH)).useDelimiter("[,\n]"); // Iterate through new lines when scanner has a next value. while (scanner.hasNextLine() && scanner.hasNext()) { // Get next values directly, without sanity checks. Object country = scanner.next(); Double gdp = scanner.nextDouble(); Object code = scanner.next(); // Output values using OUTPUT_FORMAT. Logging.log(String.format(OUTPUT_FORMAT, country, gdp, code)); } // Close scanner after completion. scanner.close(); } catch (InputMismatchException exception) { // Output unexpected InputMismatchExceptions. Logging.log(exception); } catch (Exception exception) { // Output unexpected Exceptions. Logging.log(exception, false); } } /** * Retrieves the appropriate Scanner.next[Type] method result based on passed <T>type</T>. * * @param clazz Class matching type to retrieve. * @param scanner Scanner instance from which to retrieve value. * @param <T> Type to retrieve. * @return Retrieved value, or null. */ private static <T> Object getScannerValueByType(Class<T> clazz, Scanner scanner) { switch (clazz.getSimpleName()) { case "Byte": if (scanner.hasNextByte()) return scanner.nextByte(); break; case "Double": if (scanner.hasNextDouble()) return scanner.nextDouble(); break; case "Float": if (scanner.hasNextFloat()) return scanner.nextFloat(); break; case "Integer": if (scanner.hasNextInt()) return scanner.nextInt(); break; case "Long": if (scanner.hasNextLong()) return scanner.nextLong(); break; case "Short": if (scanner.hasNextShort()) return scanner.nextShort(); break; case "String": if (scanner.hasNext()) return scanner.next(); break; default: if (scanner.hasNext()) return scanner.next(); break; } return null; } }

This code sample also uses the Logging utility class, the source of which can be found here on GitHub.

When Should You Use It?

To understand what might cause an InputMismatchException we need to briefly explore what the Scanner class is and how it might be used in a Java application. Scanner can be used to perform simple text scanning and parsing using regular expression or delimiter pattern matching. It can be used for single lines of text, or even massive files containing thousands of lines. For our sample code we’re using the 2014 GDP data courtesy of Plotly, which is in the common CSV file format. There are only a couple hundred lines of text in this file, but it should give us an interesting scenario in which to properly parse some real-world data.

To begin we start with the delimiterTestByDirectTypes method:

private static final String FILE_PATH = "2014_world_gdp_with_codes.csv"; // Pads trio of values by 35 spaces, with explicit alignment of floats on decimal point. private static final String OUTPUT_FORMAT = "%-35s%10.2f%25s"; // Matches CSVs, with first value including optional quotations, spaces, etc. private static final String REGEX_PATTERN = "("?[-()',\w\s]+"?),(\d*\.?\d*?),(\w+)n"; /** * Retrieves and outputs Scanner results using Scanner delimiter and direct next[TYPE] type method calls. */ private static void delimiterTestByDirectTypes() { try { // Create Scanner to parse passed file, using either comma- or newline-delimiter. Scanner scanner = new Scanner(new File(FILE_PATH)).useDelimiter("[,\n]"); // Iterate through new lines when scanner has a next value. while (scanner.hasNextLine() && scanner.hasNext()) { // Get next values directly, without sanity checks. Object country = scanner.next(); Double gdp = scanner.nextDouble(); Object code = scanner.next(); // Output values using OUTPUT_FORMAT. Logging.log(String.format(OUTPUT_FORMAT, country, gdp, code)); } // Close scanner after completion. scanner.close(); } catch (InputMismatchException exception) { // Output unexpected InputMismatchExceptions. Logging.log(exception); } catch (Exception exception) { // Output unexpected Exceptions. Logging.log(exception, false); } }

This method passes our local File into a new Scanner instance and specifies the use of a delimiter of "[,\n]". This delimiter is necessary because our CSV file separates each value within a record using a comma, but it also contains multiple records separated by a newline (n), so we need to inform the Scanner instance that both of these delimiters should be accounted for.

Once our Scanner instance is created we check if it has a new line and next() value, which is the default type retrieved by a Scanner instance and assumes the token it will find next is a String. From there, we directly extract each subsequent token by calling scanner.next() or scanner.nextDouble(), depending what type of value we’re extracting. Both our country and code values should be Strings, while gdp is a float or Double. Once extracted, we output everything into a clean, padded format.

The result of executing this method is as follows:

----------------- DELIMITER TEST, WITH INVALID TYPES ----------------- Afghanistan 21.71 AFG Albania 13.40 ALB Algeria 227.80 DZA American Samoa 0.75 ASM Andorra 4.80 AND Angola 131.40 AGO Anguilla 0.18 AIA Antigua and Barbuda 1.24 ATG Argentina 536.20 ARG Armenia 10.88 ARM Aruba 2.52 ABW Australia 1483.00 AUS Austria 436.10 AUT Azerbaijan 77.91 AZE [EXPECTED] java.util.InputMismatchException at java.base/java.util.Scanner.throwFor(Scanner.java:860) at java.base/java.util.Scanner.next(Scanner.java:1497) at java.base/java.util.Scanner.nextDouble(Scanner.java:2467) at io.airbrake.Main.delimiterTestByDirectTypes(Main.java:105) at io.airbrake.Main.main(Main.java:26)

So, everything was working just as expected until we reached the 15th record, at which point an InputMismatchException was thrown. If we look at the source data a bit we can see why we ran into a problem:

Afghanistan,21.71,AFG Albania,13.40,ALB Algeria,227.80,DZA American Samoa,0.75,ASM Andorra,4.80,AND Angola,131.40,AGO Anguilla,0.18,AIA Antigua and Barbuda,1.24,ATG Argentina,536.20,ARG Armenia,10.88,ARM Aruba,2.52,ABW Australia,1483.00,AUS Austria,436.10,AUT Azerbaijan,77.91,AZE "Bahamas, The",8.65,BHM

Here we see our data isn’t as “clean” as we originally thought. The “The Bahamas” is alphabetized by ignoring the word “The”, this record actually has three comma delimiters, rather than the expected three of all previous records. As a result, our first call to scanner.next() for this record returns "Bahamas, because it uses the first comma it finds as a delimiter. Thus, the next call to scanner.nextDouble() tries to evaluate the value of The" as a Double, which obviously fails, resulting in the InputMismatchException we see above.

One possible solution would be to perform some kind of sanity checks before we explicitly call the scanner.nextDouble() method. To assist with this and allow our code to be a bit more future-proof we’ve added the getScannerValueByType(Class<T> clazz, Scanner scanner) helper method:

/** * Retrieves the appropriate Scanner.next[Type] method result based on passed <T>type</T>. * * @param clazz Class matching type to retrieve. * @param scanner Scanner instance from which to retrieve value. * @param <T> Type to retrieve. * @return Retrieved value, or null. */ private static <T> Object getScannerValueByType(Class<T> clazz, Scanner scanner) { switch (clazz.getSimpleName()) { case "Byte": if (scanner.hasNextByte()) return scanner.nextByte(); break; case "Double": if (scanner.hasNextDouble()) return scanner.nextDouble(); break; case "Float": if (scanner.hasNextFloat()) return scanner.nextFloat(); break; case "Integer": if (scanner.hasNextInt()) return scanner.nextInt(); break; case "Long": if (scanner.hasNextLong()) return scanner.nextLong(); break; case "Short": if (scanner.hasNextShort()) return scanner.nextShort(); break; case "String": if (scanner.hasNext()) return scanner.next(); break; default: if (scanner.hasNext()) return scanner.next(); break; } return null; }

The purpose of this method is merely to invoke the appropriate scanner.next[Type] method based on the <T> type that was passed to it. Furthermore, we explicitly perform a sanity check using the appropriate scanner.hasNext[Type] method prior to actually returning a value, to ensure that no unexpected InputMismatchExceptions are thrown.

The delimiterTestByTypes() method is similar to our previous test, but we use the results of getScannerValueByType(...) for each record:

/** * Retrieves and outputs Scanner results using Scanner delimiter and getScannerValueByType() results. */ private static void delimiterTestByTypes() { try { // Create Scanner to parse passed file, using either comma- or newline-delimiter. Scanner scanner = new Scanner(new File(FILE_PATH)).useDelimiter("[,\n]"); // Iterate through new lines when scanner has a next value. while (scanner.hasNextLine() && scanner.hasNext()) { // Get next values indirectly through getScannerValueByType() method. Object country = getScannerValueByType(String.class, scanner); Double gdp = (Double) getScannerValueByType(Double.class, scanner); Object code = getScannerValueByType(String.class, scanner); // Output values using OUTPUT_FORMAT. Logging.log(String.format(OUTPUT_FORMAT, country, gdp, code)); } // Close scanner after completion. scanner.close(); } catch (InputMismatchException exception) { // Output unexpected InputMismatchExceptions. Logging.log(exception); } catch (Exception exception) { // Output unexpected Exceptions. Logging.log(exception, false); } }

Executing this code no longer throws any InputMismatchExceptions, but we still see some strange behavior:

---------------------- DELIMITER TEST, BY TYPES ---------------------- Afghanistan 21.71 AFG Albania 13.40 ALB Algeria 227.80 DZA American Samoa 0.75 ASM Andorra 4.80 AND Angola 131.40 AGO Anguilla 0.18 AIA Antigua and Barbuda 1.24 ATG Argentina 536.20 ARG Armenia 10.88 ARM Aruba 2.52 ABW Australia 1483.00 AUS Austria 436.10 AUT Azerbaijan 77.91 AZE "Bahamas nu The" 8.65 nu BHM

Once again, everything works fine until we get to the “Bahamas, The” record. Just as before, since our delimiter pattern is merely [,\n], the Scanner has a difficult time handling records with more than two comma-separators. In fact, the best solution is not to rely on the delimiter setting at all, but to instead use a more complex RegEx pattern to match (and then extract) each value from each record. To accomplish this we have one last method, resultStreamTest():

/** * Retrieves and outputs Scanner results using Java 9 findAll() pattern matching method. */ private static void resultStreamTest() { try { // Create Scanner to parse passed file. Scanner scanner = new Scanner(new File(FILE_PATH)); // Find all regex matches from REGEX_PATTERN. Stream<MatchResult> resultStream = scanner.findAll(REGEX_PATTERN); // Use ResultStream to collect results into a list. List<MatchResult> list = resultStream.collect(Collectors.toList()); // Iterate MatchResults to extract and output values. for (MatchResult result : list) { String country = result.group(1); Double gdp = Double.valueOf(result.group(2)); String code = result.group(3); // Output values using OUTPUT_FORMAT. Logging.log(String.format(OUTPUT_FORMAT, country, gdp, code)); } // Close scanner after completion. scanner.close(); } catch (InputMismatchException exception) { // Output unexpected InputMismatchExceptions. Logging.log(exception); } catch (Exception exception) { // Output unexpected Exceptions. Logging.log(exception, false); } }

Here we’re taking advantage of the new findAll(Pattern pattern) method added to the Scanner class in Java 9. This presents a much more modern programming pattern that allows us to chain functions, predicates, and other methods onto the iterable result of Stream<MatchResult>. In this case, we’re collecting all results into a List<MatchResult>, which we then iterate through in a for loop and extract each record’s value to be assigned to the relevant local variable and output.

The majority of the work here is accomplished in the REGEX_PATTERN passed to scanner.findAll(...):

private static final String REGEX_PATTERN = "("?[-()',\w\s]+"?),(\d*\.?\d*?),(\w+)n";

You can see the regex pattern in action on regexr.com, but the basic purpose is to ensure we’re capturing only three values from each line, and that we handle all unusual formatting and characters that are possible in the country name/first field, such as extra commas, quotation marks, parenthesis, and so forth.

The final result is an accurate and clean extraction and log output of all our GDP data:

------------------------- RESULT STREAM TEST ------------------------- Afghanistan 21.71 AFG Albania 13.40 ALB Algeria 227.80 DZA American Samoa 0.75 ASM Andorra 4.80 AND Angola 131.40 AGO Anguilla 0.18 AIA Antigua and Barbuda 1.24 ATG Argentina 536.20 ARG Armenia 10.88 ARM Aruba 2.52 ABW Australia 1483.00 AUS Austria 436.10 AUT Azerbaijan 77.91 AZE "Bahamas, The" 8.65 BHM Bahrain 34.05 BHR Bangladesh 186.60 BGD Barbados 4.28 BRB Belarus 75.25 BLR Belgium 527.80 BEL Belize 1.67 BLZ Benin 9.24 BEN Bermuda 5.20 BMU Bhutan 2.09 BTN Bolivia 34.08 BOL Bosnia and Herzegovina 19.55 BIH Botswana 16.30 BWA Brazil 2244.00 BRA British Virgin Islands 1.10 VGB Brunei 17.43 BRN Bulgaria 55.08 BGR Burkina Faso 13.38 BFA Burma 65.29 MMR Burundi 3.04 BDI Cabo Verde 1.98 CPV Cambodia 16.90 KHM Cameroon 32.16 CMR Canada 1794.00 CAN Cayman Islands 2.25 CYM Central African Republic 1.73 CAF Chad 15.84 TCD Chile 264.10 CHL China 10360.00 CHN Colombia 400.10 COL Comoros 0.72 COM "Congo, Democratic Republic of the" 32.67 COD "Congo, Republic of the" 14.11 COG Cook Islands 0.18 COK Costa Rica 50.46 CRI Cote d'Ivoire 33.96 CIV Croatia 57.18 HRV Cuba 77.15 CUB Curacao 5.60 CUW Cyprus 21.34 CYP Czech Republic 205.60 CZE Denmark 347.20 DNK Djibouti 1.58 DJI Dominica 0.51 DMA Dominican Republic 64.05 DOM Ecuador 100.50 ECU Egypt 284.90 EGY El Salvador 25.14 SLV Equatorial Guinea 15.40 GNQ Eritrea 3.87 ERI Estonia 26.36 EST Ethiopia 49.86 ETH Falkland Islands (Islas Malvinas) 0.16 FLK Faroe Islands 2.32 FRO Fiji 4.17 FJI Finland 276.30 FIN France 2902.00 FRA French Polynesia 7.15 PYF Gabon 20.68 GAB "Gambia, The" 0.92 GMB Georgia 16.13 GEO Germany 3820.00 DEU Ghana 35.48 GHA Gibraltar 1.85 GIB Greece 246.40 GRC Greenland 2.16 GRL Grenada 0.84 GRD Guam 4.60 GUM Guatemala 58.30 GTM Guernsey 2.74 GGY Guinea-Bissau 1.04 GNB Guinea 6.77 GIN Guyana 3.14 GUY Haiti 8.92 HTI Honduras 19.37 HND Hong Kong 292.70 HKG Hungary 129.70 HUN Iceland 16.20 ISL India 2048.00 IND Indonesia 856.10 IDN Iran 402.70 IRN Iraq 232.20 IRQ Ireland 245.80 IRL Isle of Man 4.08 IMN Israel 305.00 ISR Italy 2129.00 ITA Jamaica 13.92 JAM Japan 4770.00 JPN Jersey 5.77 JEY Jordan 36.55 JOR [...] Sri Lanka 71.57 LKA Sudan 70.03 SDN Suriname 5.27 SUR Swaziland 3.84 SWZ Sweden 559.10 SWE Switzerland 679.00 CHE Syria 64.70 SYR Taiwan 529.50 TWN Tajikistan 9.16 TJK Tanzania 36.62 TZA Thailand 373.80 THA Timor-Leste 4.51 TLS Togo 4.84 TGO Tonga 0.49 TON Trinidad and Tobago 29.63 TTO Tunisia 49.12 TUN Turkey 813.30 TUR Turkmenistan 43.50 TKM Tuvalu 0.04 TUV Uganda 26.09 UGA Ukraine 134.90 UKR United Arab Emirates 416.40 ARE United Kingdom 2848.00 GBR United States 17420.00 USA Uruguay 55.60 URY Uzbekistan 63.08 UZB Vanuatu 0.82 VUT Venezuela 209.20 VEN Vietnam 187.80 VNM Virgin Islands 5.08 VGB West Bank 6.64 WBG Yemen 45.45 YEM Zambia 25.61 ZMB Zimbabwe 13.74 ZWE

The Airbrake-Java library provides real-time error monitoring and automatic exception reporting for all your Java-based projects. Tight integration with Airbrake’s state of the art web dashboard ensures that Airbrake-Java gives you round-the-clock status updates on your application’s health and error rates. Airbrake-Java easily integrates with all the latest Java frameworks and platforms like Spring, Maven, log4j, Struts, Kotlin, Grails, Groovy, and many more. Plus, Airbrake-Java allows you to easily customize exception parameters and gives you full, configurable filter capabilities so you only gather the errors that matter most.

Check out all the amazing features Airbrake-Java has to offer and see for yourself why so many of the world’s best engineering teams are using Airbrake to revolutionize their exception handling practices!

Monitor Your App Free for 30 Days

Discover the power of Airbrake by starting a free 30-day trial of Airbrake. Quick sign-up, no credit card required. Get started.