Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Kotlin  Module 2 – Values, types and control flow

Parse text and format Indian numbers

Split and parse text with toIntOrNull and readlnOrNull, check formats with Regex, and print paise with the lakh and crore grouping NumberFormat lacks.

  • Beginner
  • 20 minutes
  • Examples run with Kotlin 2.4.20
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: Read text line by line, then parse it with the OrNull functions · Regex checks a format, and matches tests the whole text · Indian digit grouping needs a formatter of its own. With a pass: 2 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Split, trim and parse text with toIntOrNull, toLongOrNull and readlnOrNull without stopping on bad input
  • Validate a simple format with Regex, and choose between matches, containsMatchIn and find
  • Explain why NumberFormat for en-IN prints 123,456,789 on JDK 25
  • Format paise with lakh and crore grouping for every Long, negative amounts included
  • Parse amounts that people type into paise, and reject digits and formats the program cannot trust

Before you start

What you’ll learn · Before you start

What you will learn

  • Split, trim and parse text with toIntOrNull, toLongOrNull and readlnOrNull without stopping on bad input
  • Validate a simple format with Regex, and choose between matches, containsMatchIn and find
  • Explain why NumberFormat for en-IN prints 123,456,789 on JDK 25
  • Format paise with lakh and crore grouping for every Long, negative amounts included
  • Parse amounts that people type into paise, and reject digits and formats the program cannot trust

Before you start

On this page

"12a".toInt() stops a program with a NumberFormatException, while "12a".toIntOrNull() returns null and lets it carry on. Text from people and files is full of such values, and on JDK 25 even the formatter for English in India prints 123,456,789 where readers in India write 12,34,56,789.

This lesson shows how to parse text without letting one bad value stop the program, how to check a format with Regex and where regular expressions stop helping, and how to format Indian numbers, which the JDK’s own formatter does not do.

Read text line by line, then parse it with the OrNull functions

Every parsing function has two forms. toInt() throws when the text is not a number; toIntOrNull() returns null instead. This program feeds both the same seven texts:

Parse.kt: the same texts parsed two ways Kotlin · parse/Parse.kt
// The same texts parsed two ways: toIntOrNull() returns null, toInt() throws.
fun main() {
    for (text in listOf("42", "+42", " 42", "12a", "1,234", "2147483648", "")) {
        val strict =
            try {
                text.toInt().toString()
            } catch (e: NumberFormatException) {
                "NumberFormatException"
            }
        println("\"$text\": toIntOrNull() = ${text.toIntOrNull()}, toInt() = $strict")
    }
    println("\" 42 \".trim().toIntOrNull() = ${" 42 ".trim().toIntOrNull()}")
    println("\"2147483648\".toLongOrNull() = ${"2147483648".toLongOrNull()}")
}

Output

"42": toIntOrNull() = 42, toInt() = 42
"+42": toIntOrNull() = 42, toInt() = 42
" 42": toIntOrNull() = null, toInt() = NumberFormatException
"12a": toIntOrNull() = null, toInt() = NumberFormatException
"1,234": toIntOrNull() = null, toInt() = NumberFormatException
"2147483648": toIntOrNull() = null, toInt() = NumberFormatException
"": toIntOrNull() = null, toInt() = NumberFormatException
" 42 ".trim().toIntOrNull() = 42
"2147483648".toLongOrNull() = 2147483648

Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc Parse.kt -include-runtime -d main.jar && java -jar main.jar

A leading + or - is accepted, but a space, a comma or a letter is not, and neither is a number beyond the type: 2147483648 does not fit in an Int, so toIntOrNull() gave null and toLongOrNull() gave the number. Trim first, and choose the type by the largest value the text may hold.

The OrNull functions are the safe way to parse text that people type or that files contain. This program reads orders, one per line, from its standard input, the file orders.txt:

Totals.kt: orders read from standard input Kotlin · totals/Totals.kt
// Reads orders as "item,quantity,price in paise", one per line, until the input ends.
fun main() {
    var total = 0L
    var lineNumber = 0
    var accepted = 0
    while (true) {
        val line = readlnOrNull() ?: break
        lineNumber++
        val fields = line.split(",").map { it.trim() }
        if (fields.size != 3) {
            println("line $lineNumber rejected: ${fields.size} fields instead of 3")
            continue
        }
        val (item, quantityText, priceText) = fields
        val quantity = quantityText.toIntOrNull()
        val price = priceText.toLongOrNull()
        when {
            quantity == null -> println("line $lineNumber rejected: quantity \"$quantityText\" is not a whole number")
            price == null -> println("line $lineNumber rejected: price \"$priceText\" is not a whole number")
            else -> {
                total += quantity * price
                accepted++
                println("$item: $quantity × $price paise = ${quantity * price} paise")
            }
        }
    }
    println("Total: $total paise from $accepted of $lineNumber lines")
}

Input (standard input)

chai,3,2500
samosa,two,2000
 vada pav , 2 , 3000
lassi,1,

Output

chai: 3 × 2500 paise = 7500 paise
line 2 rejected: quantity "two" is not a whole number
vada pav: 2 × 3000 paise = 6000 paise
line 4 rejected: price "" is not a whole number
Total: 13500 paise from 2 of 4 lines

Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc Totals.kt -include-runtime -d main.jar && java -jar main.jar

Step through what happened to each of the four lines:

Interactive · stage by stage Four lines of orders.txt through Totals.kt Step through the four input lines. Each one is read, split, trimmed and parsed, then added to the total or rejected with its reason.
Four lines of orders.txt through Totals.kt, step 6 of 6. Showing readlnOrNull() (null: the input ended), split(","), trim() each field ("2" and "3000"), Parse with OrNull (price: null), Add to the total (total 13500). In focus: readlnOrNull().null: reject, next linenext linereadlnOrNull()null: the input endedsplit(",")three fields expectedtrim() each field"2" and "3000"Parse with OrNullprice: nullAdd to the totaltotal 13500

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Line 1. readlnOrNull() returns chai,3,2500. split(",") gives three fields, and toIntOrNull() and toLongOrNull() turn 3 and 2500 into numbers.
  2. Line 1 is added. 3 × 2500 is 7500 paise, which goes into the total, and the loop asks for the next line.
  3. Line 2: "two". samosa,two,2000 splits into three fields, but "two".toIntOrNull() is null. The line is rejected with its reason, and the program carries on instead of stopping.
  4. Line 3: spaces. The fields of " vada pav , 2 , 3000" carry spaces, which toIntOrNull() would refuse. trim() removes them first, so 2 × 3000 adds 6000 paise.
  5. Line 4: an empty price. lassi,1, ends with an empty field. "".toLongOrNull() is null, so this line is rejected too, with the reason the run printed.
  6. The end of the input. readlnOrNull() returns null when the input ends, and the loop stops. The program prints 13500 paise from 2 of 4 lines, where readln() would have thrown at the end instead.

readlnOrNull() returns one line, or null when the input has ended, which ends the loop; readln() would have thrown an exception there instead. Each rejected line gets its reason and the rest carry on.

Checkpoint

Check: Parsing text with the OrNull functions

  1. Question 1 of 2 What do " 42".toIntOrNull() and " 42 ".trim().toIntOrNull() give?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: null, then 42

    toIntOrNull() accepts an optional sign and digits, nothing else. Trim text from people and files before parsing it, and treat null as a value to report, not a crash.

  2. Question 2 of 2 What does readlnOrNull() return when the standard input has ended?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: null

    readln() throws at the end of the input, and readlnOrNull() returns null, so a loop that reads until the input ends uses readlnOrNull().

Regex checks a format, and matches tests the whole text

A regular expression: A pattern that describes a set of texts, such as [1-9][0-9]{5} for six digits not starting with 0. Kotlin's Regex compiles one and tests or searches text with it. describes the shape of valid text. This lesson’s own rule for a postal code, six digits with a first digit that is not 0, is [1-9][0-9]{5}:

PostalCode.kt: one pattern, three ways to apply it Kotlin · postal/PostalCode.kt
// This lesson's own rule for a postal code: six digits, the first one not 0.
val postalCode = Regex("[1-9][0-9]{5}")

fun reason(text: String): String =
    when {
        postalCode.matches(text) -> "ok"
        text.length != 6 -> "needs 6 characters, has ${text.length}"
        text.startsWith("0") -> "may not start with 0"
        else -> "only the digits 0 to 9 are allowed"
    }

fun main() {
    for (text in listOf("560001", "56001", "060001", "56000A", "5600011")) {
        println("$text: ${reason(text)}")
    }
    val address = "Deliver to 560001, Bengaluru"
    println("matches: ${postalCode.matches(address)}")
    println("containsMatchIn: ${postalCode.containsMatchIn(address)}")
    val found = postalCode.find(address)
    println("find: ${found?.value} at ${found?.range}")
}

Output

560001: ok
56001: needs 6 characters, has 5
060001: may not start with 0
56000A: only the digits 0 to 9 are allowed
5600011: needs 6 characters, has 7
matches: false
containsMatchIn: true
find: 560001 at 11..16

Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc PostalCode.kt -include-runtime -d main.jar && java -jar main.jar

matches asks whether the pattern covers the whole text, so "5600011" failed: it has one digit too many. containsMatchIn asks whether the pattern occurs anywhere, which is true for the address, and find returns the first occurrence with its position, 560001 at 11 to 16. Use matches to validate a field and find to pick a value out of longer text. Write patterns as raw strings, so a backslash such as the one in \d needs no second backslash.

A regular expression checks shape, not meaning: 999999 passes this pattern whether or not any address uses it, and a pattern of two digits, a slash and two digits accepts 99/99 as readily as a real value. Check the shape with Regex, then convert and check the value in code.

Checkpoint

Check: Checking a format with Regex

  1. Question 1 of 2 postalCode.matches(address) gave false for "Deliver to 560001, Bengaluru". Which call returns the code itself?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: postalCode.find(address)?.value

    matches checks the entire text, containsMatchIn whether a match occurs anywhere, and find returns the first match, whose value is the text it matched.

  2. Question 2 of 2 A form checks a month and day with the pattern [0-9]{2}/[0-9]{2}. Someone types 99/99, and it passes. What should the program do?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Keep the pattern for the shape, then convert the numbers and check that they make a real month and day

    Check the shape with Regex, then parse and check the value in code. For dates, a real date parser does both jobs, which the module on dates covers.

Indian digit grouping needs a formatter of its own

Large numbers are written with separators between groups of digits, and this digit grouping: The separators placed between groups of digits in a large number. International grouping uses threes, 123,456,789; Indian grouping keeps the last three and pairs the rest, 12,34,56,789. is not in threes everywhere:

Number International grouping Indian grouping
100000 100,000 1,00,000
10000000 10,000,000 1,00,00,000
123456789 123,456,789 12,34,56,789

Lakh and crore

In India the last three digits stay together and the digits before them go in pairs. A lakh: One hundred thousand in the Indian numbering system, written 1,00,000. Ten lakh make a million., written 1,00,000, is a hundred thousand; a crore: Ten million in the Indian numbering system: a hundred lakh, written 1,00,00,000., written 1,00,00,000, is ten million. ₹12,34,567.50 therefore reads as twelve lakh, thirty-four thousand, five hundred and sixty-seven rupees and fifty paise.

The JDK has a locale: A language together with a region, such as en-IN for English as used in India. Formatting functions use it to choose separators, symbols and digit grouping. for English as used in India, en-IN. This program asks its formatters for that grouping:

Grouping.kt: what NumberFormat does for en-IN on JDK 25 Kotlin · grouping/Grouping.kt
import java.text.NumberFormat
import java.util.Locale

// What the JDK's own formatters do with English as used in India.
fun main() {
    val india = Locale.of("en", "IN")
    println("number:   ${NumberFormat.getNumberInstance(india).format(123456789)}")
    println("currency: ${NumberFormat.getCurrencyInstance(india).format(1234567.5)}")
    println("format:   ${String.format(india, "%,d", 123456789)}")
    println("en-US:    ${NumberFormat.getNumberInstance(Locale.US).format(123456789)}")
}

Output

number:   123,456,789
currency: ₹1,234,567.50
format:   123,456,789
en-US:    123,456,789

Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc Grouping.kt -include-runtime -d main.jar && java -jar main.jar

All three printed groups of three. The reason is in the documentation of DecimalFormat, which NumberFormat uses: it has one grouping size, and when a pattern contains several grouping separators, only the interval after the last one counts, here 3. So to format Indian numbers correctly, a program needs one of two things:

  • A formatter of its own: about ten lines of Kotlin that group the digits of whole paise as text.
  • ICU4J, the Unicode project’s library, whose DecimalFormat has a second grouping size: #,##,### prints 123456789 as 12,34,56,789.

Key idea

Parse with the OrNull functions and give every rejected value its reason, check shapes with Regex.matches, and do not trust NumberFormat for Indian grouping: it printed 123,456,789 for en-IN.

Checkpoint

Check: Indian grouping and NumberFormat

  1. Question 1 of 2 Why did NumberFormat for en-IN print 123,456,789 on JDK 25?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: DecimalFormat has a single grouping size, the interval after the last separator, which is 3

    DecimalFormat keeps one grouping size. Indian grouping needs two, three for the last group and two for the rest, so it takes a formatter of its own or ICU4J's DecimalFormat, which has a secondary grouping size.

  2. Question 2 of 2 How is 10000000 written with Indian grouping?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 1,00,00,000

    Indian grouping keeps the last three digits together and groups the rest in pairs from the right: 1,00,00,000, which is one crore.

A formatter for lakh and crore, tested on the edges Pro: opens with a Pro, Premium or Ultimate pass

Formatting whole paise the Indian way is a few lines of work on the digits as text. This part writes the grouping, reproduces the one amount that breaks the obvious version, and leaves you to fix it.

In this part:

  • Grouping digits the Indian way, step by step
  • The smallest Long breaks the obvious version, reproduced
  • Formatting at the edge, and the inputs that guard it
  • Practise: a formatter that is right for every Long

1 diagram · 2 runnable examples (Kotlin) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Parsing amounts people type, and where Regex stops helping Pro: opens with a Pro, Premium or Ultimate pass

Amounts arrive as text such as 1,234.50, ₹ 99 or 12,34,567.5, and a payment that reads the wrong number is worse than one that refuses. This part parses typed amounts into paise and shows two traps of the parsing functions that a format check has to catch first.

In this part:

  • Digits that parse but do not match, measured
  • From typed text to paise, with the numbers
  • Limits before patterns, and messages a person can act on

2 runnable examples (Kotlin) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Practise: predict what parses

Change Parse.kt and predict first

Copy Parse.kt and add "-0", "007" and "1_000" to its list. Write down what toIntOrNull() will give for each before you run it with kotlinc Parse.kt -include-runtime -d parse.jar && java -jar parse.jar.

Finish with questions on parsing, patterns and grouping, asked weakest idea first, with the reason for every choice:

Practice that adapts to you

Practise this lesson

9 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 What does "+42".toIntOrNull() give?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: 42

    toIntOrNull() accepts an optional sign followed by decimal digits that fit in an Int.

  2. Question 2 of 9 What does "2147483648".toIntOrNull() give, and what does toLongOrNull() give for the same text?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: null, then 2147483648

    The OrNull functions return null for text that is a number but too large for the type, so choose the type by the largest value the text may hold.

  3. Question 3 of 9 An import reads 10,000 order lines, and 3 of them hold a quantity such as "two". What should it do?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: Skip each bad line, report its line number and reason, and import the rest

    Parse with the OrNull functions, give every rejected value its reason and its line, and say how many lines were accepted, so the result can be trusted and the rest fixed.

  4. Question 4 of 9 What does Regex("[0-9]{3}").matches("1234") give?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: false

    matches checks the entire input. containsMatchIn("1234") would be true, and find would return 123.

  5. Question 5 of 9 A field must hold a postal code and nothing else. Which check is right?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: postalCode.matches(text)

    Use matches to validate a whole field and find to pick a value out of a longer text.

  6. Question 6 of 9 Why are patterns usually written as raw strings, such as """\d{6}"""?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: A backslash is plain text in a raw string, so the pattern is written once, as the regex engine reads it

    Patterns are full of backslashes. A raw string keeps each as written, so the pattern in the code is the pattern the engine sees.

  7. Question 7 of 9 Which is 12,34,56,789 written with international grouping?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: 123,456,789

    Both groupings write the same number, 123456789; only where the commas go differs.

  8. Question 8 of 9 Which library can print Indian grouping from a pattern, because its DecimalFormat has a secondary grouping size?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: ICU4J, with a pattern such as

    The JDK's formatters keep one grouping size. ICU4J's DecimalFormat keeps two, so Indian grouping is a pattern there; without the library, a short formatter of your own does it.

  9. Question 9 of 9 A report must show amounts the way readers in India expect, the same on every server. Which approach can you rely on?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: A formatter of your own that groups the digits of whole paise, or ICU4J

    Indian grouping needs two grouping sizes, which the JDK's own formatters do not have. Write the grouping yourself or use a library that has it, and test it on the edges.

Regex Tester Try a pattern such as [1-9][0-9]{5} against your own inputs and see each match and its position. Lakh Crore Converter (to Million, Billion) Convert a number between Indian and international grouping and units to check your formatter.

Interview questions

  1. Fresher · Warm-up

    What is the difference between toInt() and toIntOrNull() in Kotlin, and when do you use each?

    toInt() parses a string into an Int and throws a NumberFormatException when the text is not a valid number: "12a", " 42" with its space, "1,234", or a number beyond the Int range such as "2147483648". toIntOrNull() accepts exactly the same texts but returns null instead of throwing, so the caller decides what a bad value means. Use toIntOrNull() for text that people type or files contain, where bad values are expected: trim it first, and report every null with its reason instead of stopping. Keep toInt() for text that can only be wrong because of a bug, such as a constant in your own code, so the exception points at that bug. The same pair exists for Long and Double, and the Long version takes numbers past 2,147,483,647.

  2. Mid to senior

    A payments form accepts amounts typed as 1,234.50, ₹ 99 or 12,34,567.5. Design a function that turns the text into paise, say what it refuses and why, and explain why calling toDouble() first is the wrong start.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Fresher to mid

    A finance report formatted with NumberFormat for en-IN shows 12,345,678 where the finance team expects 1,23,45,678. Explain why, and how you would produce Indian grouping reliably on every server, negative amounts included.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    A teammate wants one regular expression to validate the dates, amounts and postal codes typed into a form. When is a regular expression the right check, and when would you parse instead? Give an example where a pattern accepts text your program cannot use.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • toInt() throws on text that is not a number and toIntOrNull() returns null, so parse text from people and files with the OrNull functions, after trim(), and give every rejected value its reason.
  • readlnOrNull() returns null when the input ends, while readln() throws, so a loop over standard input stops cleanly on null.
  • Regex.matches checks the whole text, containsMatchIn and find look inside it, and a pattern checks only the shape of a value, never its meaning.
  • On JDK 25, NumberFormat for en-IN printed 123,456,789, because DecimalFormat has a single grouping size, so Indian grouping needs a formatter of its own or ICU4J.

Terms in this lesson

crore
Ten million in the Indian numbering system: a hundred lakh, written 1,00,00,000.
digit grouping
The separators placed between groups of digits in a large number. International grouping uses threes, 123,456,789; Indian grouping keeps the last three and pairs the rest, 12,34,56,789.
lakh
One hundred thousand in the Indian numbering system, written 1,00,000. Ten lakh make a million.
locale
A language together with a region, such as en-IN for English as used in India. Formatting functions use it to choose separators, symbols and digit grouping.
regular expression
A pattern that describes a set of texts, such as [1-9][0-9]{5} for six digits not starting with 0. Kotlin's Regex compiles one and tests or searches text with it.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.