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.

Java  Module 2 – Variables, types and operators

Variables and the eight primitive types

How to declare Java variables with a type or with var, the size and range of the eight primitive types, and how to write int, long, hex and binary literals.

  • Beginner
  • 20 minutes
  • Examples run with Java 25.0.4 and Python 3.14.8
  • By MySmartCoPilot
Free: 20 minutes

What you will learn

  • Declare variables with explicit types and with var, and predict the type var infers
  • State the size and range of each primitive type
  • Choose a primitive type for a value from its range, its fraction and how many values you keep
  • Write numeric literals with underscores, hex, binary and long or float suffixes
  • Explain which variables start with a default value and when the compiler refuses an unassigned local

Before you start

What you’ll learn · Before you start

What you will learn

  • Declare variables with explicit types and with var, and predict the type var infers
  • State the size and range of each primitive type
  • Choose a primitive type for a value from its range, its fraction and how many values you keep
  • Write numeric literals with underscores, hex, binary and long or float suffixes
  • Explain which variables start with a default value and when the compiler refuses an unassigned local

Before you start

On this page

A variable is a named place that holds a value, and in Java every variable has a type that the compiler knows before the program runs. The type decides which values fit and which operations make sense: int seats = 120; declares an int called seats and stores 120 in it, while int seats = 3000000000; does not even compile, because an int stops at 2,147,483,647. Java has eight primitive type: One of the eight value types built into Java: byte, short, int, long, float, double, char and boolean. A variable of a primitive type holds the value itself and has no methods. for plain values: four for whole numbers (byte, short, int and long), two for numbers with a fractional part (float and double), char for one UTF-16 code unit and boolean for true or false. Everything else, from String to the classes you write, is a reference type: Every type that is not primitive: classes such as String, interfaces and arrays. A variable of a reference type holds a reference to an object, or null..

You will declare variables with a type and with var, step through the type the compiler gives each one, print the limits of every primitive type on JDK 25, choose a type for a value by answering four questions, and write one number in four bases. The last section shows why the compiler refuses to read a local variable that might have no value.

A declaration fixes the variable’s type

A declaration names the type first, then the variable, and usually gives it a first value. Assigning again replaces the value; final forbids that, so a final variable keeps its first value for good:

Declare.java and VarErrors.java

Java (Declare.java) · Declare.java

// Variables with their types written out, then with var, which works the type out from the value.
void main() {
    int seats = 120;
    double ticketPrice = 249.50;
    char row = 'K';
    boolean soldOut = false;
    long bytesStreamed = 7_340_032_000L;
    seats = seats - 2;            // assigning again changes the value
    final int screens = 4;        // final: this one is never assigned again

    var shows = 3;                // 3 is an int, so shows is an int
    var halfPrice = ticketPrice / 2;
    var initial = 'J';
    var total = bytesStreamed * shows;

    IO.println("seats = " + seats + ", row " + row + ", sold out: " + soldOut + ", screens: " + screens);
    IO.println("shows is " + typeOf(shows) + ", halfPrice is " + typeOf(halfPrice) + " (" + halfPrice + ")");
    IO.println("initial is " + typeOf(initial) + ", total is " + typeOf(total) + " (" + total + ")");
}

// One method per type: Java picks the one that matches the type the compiler gave the variable.
String typeOf(int x) { return "int"; }
String typeOf(long x) { return "long"; }
String typeOf(double x) { return "double"; }
String typeOf(char x) { return "char"; }

Java (VarErrors.java) · VarErrors.java

void main() {
    var count;                    // no value to work the type out from
    var nothing = null;           // null has no type of its own
    var total = 10;               // total is an int from here on
    total = 9_000_000_000L;       // a long value does not fit in it
}
Both files, run with JDK 25 Python · types_session.py
from session import run, version

version()
run("""
java Declare.java
!java VarErrors.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Declare.java
seats = 118, row K, sold out: false, screens: 4
shows is int, halfPrice is double (124.75)
initial is char, total is long (22020096000)
$ java VarErrors.java
VarErrors.java:2: error: cannot infer type for local variable count
    var count;                    // no value to work the type out from
        ^
  (cannot use 'var' on variable without initializer)
VarErrors.java:3: error: cannot infer type for local variable nothing
    var nothing = null;           // null has no type of its own
        ^
  (variable initializer is 'null')
VarErrors.java:5: error: incompatible types: possible lossy conversion from long to int
    total = 9_000_000_000L;       // a long value does not fit in it
            ^
3 errors
error: compilation failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 types_session.py

Each run on this page is a short terminal session. The line starting with # names the JDK, a line starting with $ is a command, and the lines under it are what that command printed. java Declare.java compiles the file in memory and runs it, so one command is enough. To try an example yourself, save the file and type the same command in a terminal, in the folder where the file is.

var asks the compiler to take the type from the value on the right, which is called local variable type inference: Writing var instead of a type for a local variable with an initial value: the compiler gives the variable the type of that value, so var shows = 3; declares an int. The type is fixed when the file compiles.. 3 is an int, so shows is an int; ticketPrice / 2 is a double; 'J' is a char; and a long times an int is a long. The file proves it with four methods called typeOf, one per type: Java calls the method whose parameter type matches the variable’s type, so each line of output names the type the compiler chose. Step through the four declarations, then the three lines of VarErrors.java that the compiler refuses:

Interactive · step by step The type the compiler gives each var declaration Step through Declare.java's four var lines, then the three lines of VarErrors.java that the compiler refuses.
The type the compiler gives each var declaration, step 9 of 9. Showing var shows, int, var halfPrice, double, var initial, char, var total, long, var count;, Refused, var nothing, Refused, var total = 10, Refused. In focus: int, double, char, long.infersinfersinfersinfersvar shows= 3int3 is an int literalvar halfPrice= ticketPrice / 2doublea double / an intvar initial= 'J'chara char literalvar total= bytesStreamed * showslonga long * an intvar count;no value at allRefusednothing to infer fromvar nothing= nullRefusednull names no single typevar total = 10then = 9_000_000_000LRefuseda long does not fit an int

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

Every step as text
  1. Four lines with var. Declare.java declares four variables with var and no type. For each one the compiler looks at the value after the = sign and gives the variable that value's type, before the program runs.
  2. 3 is an int. 3 is an int literal, so shows is an int. The method typeOf(int) is the one Java picked for it, and the run printed shows is int.
  3. A double divided by 2. ticketPrice is a double, and a double divided by the int 2 is a double, so halfPrice is a double. The run printed halfPrice is double (124.75).
  4. A char literal. 'J' in single quotes is a char literal, so initial is a char: the run printed initial is char.
  5. A long times an int. bytesStreamed is a long, and a long times an int is a long, so total is a long. The run printed 22020096000, a value no int can hold.
  6. No value, no type. VarErrors.java tries var count; with no value. There is nothing to take a type from, so the compiler refuses the line with cannot use 'var' on variable without initializer.
  7. null names no type. var nothing = null; is refused as well. null fits every reference type, so it names none of them, and the message says variable initializer is 'null'.
  8. The type stays fixed. var total = 10; makes total an int for good. Assigning 9_000_000_000L to it later is a possible lossy conversion from long to int, exactly as after int total = 10;.
  9. Chosen at compile time. Every type in this figure was chosen when the file was compiled. var changes what you type, not what the program does, which is why it is called type inference and not dynamic typing.

The type is fixed at compile time, which is why VarErrors.java does not compile. var count; has no value to take a type from, null belongs to no single type, and once total is an int, a long value no longer fits in it. So var is type inference, not dynamic typing: the compiled program is the same as if you had written the type yourself. It works only for local variables that get a value where they are declared, loop variables included; fields, method parameters and return types always name their type.

Reading var as a variable of any type

var total = 10; followed by total = 9_000_000_000L; does not compile, because the first line made total an int. When the variable must hold larger values later, write long total = 10; or var total = 10L;.

Use var when the type is obvious from the right-hand side, as in var names = new ArrayList<String>(). Write the type out when a reader would otherwise have to look elsewhere to learn it, as in var result = process(order).

Checkpoint

Check: Declaring with a type or with var

  1. Question 1 of 2 In Declare.java bytesStreamed is a long and shows an int. Which type does var total = bytesStreamed * shows; give total?

    Choose one answer.

    A hint

    Work out the type of the value on the right first; var copies it.

    Show the answer: Question 1 of 2

    Answer: long

    var takes the type of its initial value. bytesStreamed * shows multiplies a long by an int, which Java does in long, so total is a long, as the run printed: total is long (22020096000).

  2. Question 2 of 2 var total = 10; is followed by total = 9_000_000_000L;. What happens?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It does not compile: total is an int, and a long value does not fit in it

    var total = 10; makes total an int for good. The later assignment is refused with possible lossy conversion from long to int, the error VarErrors.java shows. Write long total = 10; or var total = 10L; when it must hold larger values.

Each primitive type has a fixed range on every computer

Each primitive type takes a fixed number of bits. The diagram draws each type to scale, one box for every 8 bits:

Bars showing the size of each Java primitive type: byte 8 bits, short and char 16, int and float 32, long and double 64.Bits in each primitive typeone box for every 8 bits (one byte)byte8 bits: -128 to 127short16 bits: -32,768 to 32,767char16 bits: 0 to 65,535 (a UTF-16 code unit)int32 bits: about ±2.1 billionlong64 bits: about ±9.2 × 10¹⁸float32 bits: about 7 significant digitsdouble64 bits: 15 to 16 significant digitsbooleantrue or false; the language does not fix its size

The eight primitive types, drawn to scale by their size in bits

Text description of the diagram

One row per primitive type, with a box for every 8 bits.

  • byte: 8 bits, from -128 to 127.
  • short: 16 bits, from -32,768 to 32,767.
  • char: 16 bits, from 0 to 65,535, one UTF-16 code unit.
  • int: 32 bits, about plus or minus 2.1 billion.
  • long: 64 bits, about plus or minus 9.2 × 10¹⁸.
  • float: 32 bits, about 7 significant decimal digits.
  • double: 64 bits, about 15 to 16 significant decimal digits.
  • boolean: true or false; the language does not fix how many bits it takes.

The table adds the exact ranges, the value each type starts with when it is a field, and what it is usually for:

Type and size Range Default in a field Typical use
byte, 8 bits -128 to 127 0 raw bytes of files and network data
short, 16 bits -32,768 to 32,767 0 large arrays of small numbers, rarely anything else
int, 32 bits -2,147,483,648 to 2,147,483,647 0 the default for whole numbers: counts, indexes, sizes
long, 64 bits about ±9.2 × 10¹⁸ 0L totals, IDs, times in milliseconds, amounts in cents
float, 32 bits about ±3.4 × 10³⁸, 7 significant digits 0.0f large arrays where memory matters, such as graphics
double, 64 bits about ±1.8 × 10³⁰⁸, 15 to 16 significant digits 0.0 the default for numbers with a fractional part
char, 16 bits 0 to 65,535 '\u0000' one UTF-16 code unit, usually one character
boolean, size not fixed true or false false conditions and yes-or-no facts

The four whole-number types are signed: each stores negative and positive values in two's complement: How Java stores signed whole numbers: the highest bit counts as minus 2 to the power n − 1, so an n-bit type runs from −2ⁿ⁻¹ to 2ⁿ⁻¹ − 1, and the byte 11111111 is -1., so a type of n bits runs from −2ⁿ⁻¹ to 2ⁿ⁻¹ − 1. char is the only unsigned one. float and double are the IEEE 754 binary32 and binary64 formats, which Floating point, rounding and BigDecimal explains. The Java Language Specification fixes every one of these ranges, so they are the same on every computer and every JVM. The classes Byte, Short, Integer, Long, Float, Double and Character hold the limits as constants, so you never need to type them from memory:

Ranges.java Java
// The size and the range of each numeric primitive type, from the constants of its wrapper class.
void main() {
    IO.println("byte    " + Byte.SIZE + " bits  " + Byte.MIN_VALUE + " to " + Byte.MAX_VALUE);
    IO.println("short   " + Short.SIZE + " bits  " + Short.MIN_VALUE + " to " + Short.MAX_VALUE);
    IO.println("char    " + Character.SIZE + " bits  " + (int) Character.MIN_VALUE + " to " + (int) Character.MAX_VALUE);
    IO.println("int     " + Integer.SIZE + " bits  " + Integer.MIN_VALUE + " to " + Integer.MAX_VALUE);
    IO.println("long    " + Long.SIZE + " bits  " + Long.MIN_VALUE + " to " + Long.MAX_VALUE);
    IO.println("float   " + Float.SIZE + " bits  largest " + Float.MAX_VALUE + ", smallest above zero " + Float.MIN_VALUE);
    IO.println("double  " + Double.SIZE + " bits  largest " + Double.MAX_VALUE + ", smallest above zero " + Double.MIN_VALUE);
}
The limits, printed by JDK 25 Python · ranges_session.py
from session import run, version

version()
run("""
java Ranges.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Ranges.java
byte    8 bits  -128 to 127
short   16 bits  -32768 to 32767
char    16 bits  0 to 65535
int     32 bits  -2147483648 to 2147483647
long    64 bits  -9223372036854775808 to 9223372036854775807
float   32 bits  largest 3.4028235E38, smallest above zero 1.4E-45
double  64 bits  largest 1.7976931348623157E308, smallest above zero 4.9E-324

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 ranges_session.py

Look at the last two lines. Float.MIN_VALUE and Double.MIN_VALUE are not the most negative values: they are the smallest values above zero, 1.4 × 10⁻⁴⁵ and 4.9 × 10⁻³²⁴. The most negative double is -Double.MAX_VALUE. For the whole-number types, MIN_VALUE really is the most negative value.

Starting a search for the largest double at Double.MIN_VALUE

double highest = Double.MIN_VALUE; starts just above zero, so when every reading is negative, highest never changes and the program reports 4.9E-324. Start from -Double.MAX_VALUE, or from the first reading itself.

Programmer Calculator (Bitwise) See a number as 8, 16, 32 or 64 bits and watch where it stops fitting.

Checkpoint

Check: The eight primitive types and their ranges

  1. Question 1 of 2 A whole-number type of n bits holds −2ⁿ⁻¹ to 2ⁿ⁻¹ − 1. What is the largest value a short, which has 16 bits, can hold?

    Type a number.

    A hint

    2 to the power 15 is 32,768.

    Show the answer: Question 1 of 2

    Answer: 32767

    With n = 16 the largest value is 2¹⁵ − 1 = 32,768 − 1 = 32,767, which is what Short.MAX_VALUE printed in Ranges.java. The most negative value is −2¹⁵ = −32,768.

  2. Question 2 of 2 Which expression gives the most negative double?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: -Double.MAX_VALUE

    Double.MIN_VALUE is the smallest positive double, not the most negative one. The most negative double is -Double.MAX_VALUE. Only for the whole-number types is MIN_VALUE the most negative value.

Choose int, and long once a value can pass 2.1 billion

Most values need one of four types, and a few facts decide which:

  • int for whole numbers, unless a value can pass about 2.1 billion (2,147,483,647, which is 2³¹ − 1). Counts, loop indexes and the sizes of strings and arrays are int.
  • long when a value can grow past that. Totals, database IDs, file sizes and time stamps belong here: System.currentTimeMillis() returns a long, because the milliseconds since the start of 1970 are already past 1.7 × 10¹², and an int stops at 2.1 × 10⁹.
  • double for numbers with a fractional part, such as measurements and averages. Not for money: Floating point, rounding and BigDecimal shows why, and what to use instead.
  • byte for binary data. Mind the sign: a byte holds -128 to 127, so the byte 0xFF of a file is -1 in a byte. That is why InputStream.read() returns an int from 0 to 255, with -1 meaning the end of the stream.
  • short and float save memory and little else. An array of a million int values takes about 4 MB and the same array of short values about 2 MB; that matters only for large arrays.
  • char for one UTF-16 code unit and boolean for a condition. char, boolean, casting and conversions covers both.

The table below answers back. Pick a value you want to store, such as the bytes of a photo, the milliseconds a video has played or the average mark of a class, answer its four questions, and watch which types are left and why:

Interactive · answer and compare Which numeric type fits your value? Answer up to four questions about the value you want to store. The table marks the types that fit, and says why the others do not.
Can the value have a fractional part, such as 2.5 kg or an average?
Can the value ever pass 2,147,483,647, about 2.1 billion, in either direction?
Is it raw data, the bytes of a file or of a network message?
Will you keep millions of these values in one array?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Six numeric primitive types against the four things that decide between them
Type Whole numbers it holds Fractions A million of them In everyday code
byte Size: 8 bits, 1 byte Poor -128 to 127 only Poor none: 7.0 does not even go in without a cast Good about 1 MB Mixed arithmetic on it gives an int, so a result needs a cast to go back
short Size: 16 bits, 2 bytes Poor -32,768 to 32,767 Poor none Good about 2 MB Mixed arithmetic on it gives an int, so a result needs a cast to go back
int Size: 32 bits, 4 bytes Good about ±2.1 billion, up to 2,147,483,647 Poor none Mixed about 4 MB Good the type of whole-number literals, counts, indexes and lengths
long Size: 64 bits, 8 bytes Good about ±9.2 × 10¹⁸ Poor none Poor about 8 MB Good needs an L on literals past the int range: 12_000_000_000L
float Size: 32 bits, 4 bytes Poor exact only up to 16,777,216 (2²⁴) Mixed about 7 significant digits Mixed about 4 MB Mixed needs an f on literals: 19.99f
double Size: 64 bits, 8 bytes Mixed exact up to 2⁵³, about 9 × 10¹⁵ Good 15 to 16 significant digits Poor about 8 MB Good the type of decimal literals such as 249.50
How the answers decide
  • “Can the value have a fractional part, such as 2.5 kg or an average?” Yes, it can have a fraction: rules out byte, short, int and long. The four whole-number types hold no fraction at all: FitErrors.java shows that even 7.0 does not go into an int without a cast.
  • “Can the value have a fractional part, such as 2.5 kg or an average?” Yes, it can have a fraction and “Will you keep millions of these values in one array?” No, a few at a time: adds a caveat to float. A float keeps only about 7 significant digits and saves memory only in large arrays; double is the usual type for fractions.
  • “Can the value have a fractional part, such as 2.5 kg or an average?” No, whole numbers only: rules out float. A float holds whole numbers exactly only up to 16,777,216, so larger counts and IDs change without a word.
  • “Can the value have a fractional part, such as 2.5 kg or an average?” No, whole numbers only: adds a caveat to double. A double holds whole numbers exactly up to 2⁵³, but counts, indexes and IDs stay exact, compare exactly and read clearly in an integer type.
  • “Can the value ever pass 2,147,483,647, about 2.1 billion, in either direction?” Yes, or I am not sure: rules out byte, short and int. An int stops at 2,147,483,647, and past it the value wraps round without an error. Totals, IDs, file sizes and times in milliseconds need a long.
  • “Will you keep millions of these values in one array?” No, a few at a time: adds a caveat to byte and short. byte and short save memory only in large arrays, and arithmetic on them gives an int that needs a cast to be stored back.
  • “Is it raw data, the bytes of a file or of a network message?” Yes: adds a caveat to byte. A byte is signed: the file byte 0xFF reads as -1. That is why InputStream.read() returns an int from 0 to 255, with -1 for the end.
  • “Will you keep millions of these values in one array?” Yes, millions: adds a caveat to long and double. In a large array every byte of the type counts: a million long or double values take about 8 MB, twice as much as int or float.

In short: int for whole numbers, long once they can pass 2,147,483,647, double for fractions, byte for raw data, and short and float only in large arrays where memory matters.

Checkpoint

Check: Choosing a type for a value

  1. Question 1 of 2 Which type is the usual choice for a count, a loop index or the length of a string?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: int

    int is the default for whole numbers: counts, loop indexes and the sizes of strings and arrays. Move to long only when a value can pass 2,147,483,647.

  2. Question 2 of 2 A field counts the milliseconds a video has played, and the longest video is 3 hours. Which is the narrowest type that always holds the count?

    Choose one answer.

    A hint

    3 hours is 3 × 60 × 60 × 1,000 milliseconds.

    Show the answer: Question 2 of 2

    Answer: int

    3 × 60 × 60 × 1,000 = 10,800,000 milliseconds fits easily in an int. An int would overflow only past 2,147,483,647 milliseconds, about 24.8 days, which is when a count of milliseconds since 1970 needs a long.

A literal’s form decides its base and its type

A value written in the code is a literal: A value written directly in the code, such as 120, 0xFF, 12_000_000_000L, 19.99f or true. Whole-number literals are int unless they end in L, and decimal literals are double unless they end in f.. Whole-number literals are int unless they end in L, and numbers with a decimal point or an exponent are double unless they end in f:

Literals.java, LiteralErrors.java and FitErrors.java

Java (Literals.java) · Literals.java

// One value can be written in several ways; the compiler stores the same bits.
void main() {
    int decimal = 255;
    int hex = 0xFF;
    int binary = 0b1111_1111;
    int octal = 0377;             // a leading 0 means base 8
    int surprise = 010;           // base 8 again: this is eight, not ten
    IO.println(decimal + " " + hex + " " + binary + " " + octal + " " + surprise);

    long bigCount = 12_000_000_000L;   // the L makes it a long literal
    long grouped = 12_00_000L;         // underscores may go between any two digits
    IO.println(bigCount + " " + grouped + " " + (grouped == 1200000));

    float ratio = 1.5f;           // the f makes it a float literal
    double tiny = 1e-9;           // 1 × 10 to the power -9
    double large = 2.5e6;         // 2,500,000
    IO.println(ratio + " " + tiny + " " + large);

    char letter = 'A';
    char sameLetter = '\u0041';  // a Unicode escape for the same character
    IO.println(letter + " " + sameLetter + " " + (letter == sameLetter));
}

Java (LiteralErrors.java) · LiteralErrors.java

void main() {
    int big = 3000000000;         // more than an int can hold
    long count = 1_000_;          // an underscore at the end of a number
}

Java (FitErrors.java) · FitErrors.java

void main() {
    byte level = 200;             // a byte holds -128 to 127
    float price = 19.99;          // 19.99 is a double literal
    int whole = 7.0;              // a double does not go into an int on its own
}
The literals, then two files the compiler refuses Python · literals_session.py
from session import run, version

version()
run("""
java Literals.java
!java LiteralErrors.java
!java FitErrors.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Literals.java
255 255 255 255 8
12000000000 1200000 true
1.5 1.0E-9 2500000.0
A A true
$ java LiteralErrors.java
LiteralErrors.java:2: error: integer number too large
    int big = 3000000000;         // more than an int can hold
              ^
LiteralErrors.java:3: error: illegal underscore
    long count = 1_000_;          // an underscore at the end of a number
                      ^
2 errors
error: compilation failed
[exit status 1]
$ java FitErrors.java
FitErrors.java:2: error: incompatible types: possible lossy conversion from int to byte
    byte level = 200;             // a byte holds -128 to 127
                 ^
FitErrors.java:3: error: incompatible types: possible lossy conversion from double to float
    float price = 19.99;          // 19.99 is a double literal
                  ^
FitErrors.java:4: error: incompatible types: possible lossy conversion from double to int
    int whole = 7.0;              // a double does not go into an int on its own
                ^
3 errors
error: compilation failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 literals_session.py

The first line of the run is one number written four ways, followed by a surprise. Read each form against the value it printed:

In the code Base Value printed
255 10 255
0xFF 16 255
0b1111_1111 2 255
0377 8 255
010 8 8, not 10
  • 0x starts a hexadecimal literal and 0b a binary one. A leading 0 alone means octal, so 010 is eight. Few programs need octal, and the leading zero is easy to type by mistake, so never pad numbers with zeros.
  • Underscores may sit between two digits to make long numbers readable. They change nothing: 12_000_000_000L is 12000000000L. They must sit between digits, so 1_000_ and 1_000_L are errors.
  • Write L in capitals: a small l looks like the digit 1.
  • 1e-9 is 1 × 10⁻⁹. 'A' is a char literal, and so is '\u0041', the same character written as a Unicode escape.

The compiler refuses a literal that does not fit. 3000000000 is too large for any int, so it needs an L. A constant that fits may go straight into a smaller variable, as in byte level = 120;, but 200 does not fit in a byte. 19.99 is a double, so a float needs 19.99f, and a double never goes into an int without a cast.

Lakh and crore grouping

Underscores may sit between any two digits, so Indian digit grouping compiles: 12_00_000L is 12 lakh, the same value as 1_200_000L, as the second line of the run shows. Write the grouping your readers expect; the compiler ignores it.

Number Base Converter Convert a value between decimal, hexadecimal, binary and octal before you write it as a literal.

Key idea

A literal’s prefix picks its base (0x, 0b, a leading 0), its suffix picks its type (L for long, f for float), and underscores between digits change nothing.

Checkpoint

Check: Writing literals

  1. Question 1 of 2 What does IO.println(011 + 0x11 + 0b11); print?

    Type a number.

    A hint

    Read each prefix first. A leading 0 alone means base 8.

    Show the answer: Question 1 of 2

    Answer: 29

    011 is octal, 9; 0x11 is hexadecimal, 17; 0b11 is binary, 3. 9 + 17 + 3 = 29.

  2. Question 2 of 2 Which of these declarations compiles?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: float ratio = 1.5f;

    A literal's suffix decides its type. 1.5f is a float; 1.5 is a double; 3000000000 is an int literal, which is too large, so it needs an L; and 200 is outside the byte range.

Fields start at a default, local variables must be assigned

Fields, the variables that belong to a class or an object, start with a default value: zero for numbers, '\u0000' for char, false for boolean and null for references. Local variables, the ones declared inside a method, get no default. The compiler checks every path through the method and refuses to read a local variable that might not have been assigned:

Defaults.java and Unassigned.java

Java (Defaults.java) · Defaults.java

// Fields get a default value; this file's fields are fields of the class the compiler makes for it.
int seats;
double price;
boolean open;
char initial;
String name;

void main() {
    IO.println("int " + seats + ", double " + price + ", boolean " + open);
    IO.println("char code " + (int) initial + ", String " + name);
}

Java (Unassigned.java) · Unassigned.java

void main() {
    int total;
    int items = 3;
    if (items > 0) {
        total = items * 10;
    }
    IO.println(total);            // not assigned when items is 0 or less
}
Fields start at their defaults; a local must be assigned first Python · defaults_session.py
from session import run, version

version()
run("""
java Defaults.java
!java Unassigned.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Defaults.java
int 0, double 0.0, boolean false
char code 0, String null
$ java Unassigned.java
Unassigned.java:7: error: variable total might not have been initialized
    IO.println(total);            // not assigned when items is 0 or less
               ^
1 error
error: compilation failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 defaults_session.py

In Unassigned.java the compiler cannot know whether items > 0 will be true, so the path that skips the if leaves total unassigned and the program does not compile. Step through the two paths the compiler follows:

Interactive · stage by stage How the compiler checks total before it is read Step through the two paths from the declaration of total to the line that reads it, then the fix.
How the compiler checks total before it is read, step 5 of 5. Showing int total; (int total = 0; fixes it), if (items > 0), total = items * 10, IO.println(total).false: skips the assignmentint total;int total = 0; fixes itif (items > 0)true or false?total = items * 10assigns totalIO.println(total)reads total

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

Every step as text
  1. A local starts empty. int total; declares a local variable and gives it no value. A field would start at 0, but a local variable gets no default at all.
  2. The compiler sees two paths. The compiler does not run the program, so it cannot know whether items > 0 will be true. It follows both ways out of the if.
  3. The true path. When the test is true, total = items * 10 assigns total, so on this path IO.println(total) reads a value.
  4. The false path. When the test is false, the assignment is skipped and IO.println(total) is reached with nothing in total. One such path is enough: the compiler stops with variable total might not have been initialized.
  5. The fix. Give total a value on every path: a starting value, int total = 0;, or an else that assigns it. Both ways into IO.println(total) then carry a value, and the file compiles.

Give such a variable a value on every path, often with an else, or at its declaration when a sensible starting value exists. This rule, called definite assignment: The compiler rule that a local variable must be assigned on every path through the code before it is read. Otherwise the file does not compile: variable might not have been initialized., turns a classic bug of other languages, reading memory that was never set, into a compile-time error.

Key idea

A field starts at 0, false, '\u0000' or null; a local variable starts with nothing, and the compiler refuses to read it until every path to that line has assigned it.

Checkpoint

Check: Default values and definite assignment

  1. Question 1 of 2 A file declares the fields int seats;, boolean open; and String name; and never assigns them. What does main print for them?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 0, false and null

    Fields get default values: 0, 0.0, false, '\u0000' or null. Local variables get none, and the compiler refuses to read one that might be unassigned.

  2. Question 2 of 2 int total; is followed by if (items > 0) total = items * 10; and then IO.println(total);, which does not compile. Which change makes it compile?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Add else total = 0; to the if

    Definite assignment means a value on every path to the line that reads the variable. An else, or a starting value such as int total = 0;, gives one. Two separate ifs do not, because the compiler checks the paths, not what the conditions mean.

Practise: the narrowest type for a range

Write narrowest(min, max), which returns "byte", "short", "int" or "long": the narrowest whole-number type that can hold every value from min to max. The checks in main describe eight quantities, and each must say ok:

Narrowest.java Java
// Exercise: narrowest(min, max) returns the name of the narrowest integer type, "byte", "short", "int" or
// "long", that holds every whole number from min to max. Write it, then run this file: every check must say ok.
String narrowest(long min, long max) {
    return "long";                // replace this line
}

int failures = 0;

void check(String quantity, long min, long max, String expected) {
    String got = narrowest(min, max);
    if (!got.equals(expected)) failures++;
    IO.println((got.equals(expected) ? "ok      " : "FAILED  ") + quantity + ": " + got);
}

void main() {
    check("a temperature in degrees Celsius, -90 to 60", -90, 60, "byte");
    check("a level from -128 to 127", -128, 127, "byte");
    check("a byte read from a file, 0 to 255", 0, 255, "short");
    check("a year from 1 to 9999", 1, 9999, "short");
    check("a change in stock from -40000 to 40000", -40_000, 40_000, "int");
    check("pixels in a 10000 by 10000 image", 0, 100_000_000, "int");
    check("a balance in cents up to 50 million in either direction", -5_000_000_000L, 5_000_000_000L, "long");
    check("milliseconds in 100 years", 0, 3_155_760_000_000L, "long");
    IO.println(failures + " of 8 checks failed");
    if (failures > 0) System.exit(1);
}

As given, the method always answers "long", so six of the eight checks fail:

The exercise before you start Python · exercise_session.py
from session import run, version

version()
run("""
!java Narrowest.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Narrowest.java
FAILED  a temperature in degrees Celsius, -90 to 60: long
FAILED  a level from -128 to 127: long
FAILED  a byte read from a file, 0 to 255: long
FAILED  a year from 1 to 9999: long
FAILED  a change in stock from -40000 to 40000: long
FAILED  pixels in a 10000 by 10000 image: long
ok      a balance in cents up to 50 million in either direction: long
ok      milliseconds in 100 years: long
6 of 8 checks failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exercise_session.py

First hint: compare min and max with Byte.MIN_VALUE and Byte.MAX_VALUE first, then with the limits of Short and Integer; whatever is left needs a long. Notice the third check: a byte read from a file can be 255, which a byte cannot hold.

When all eight checks say ok, check the five ideas of this lesson. The questions come one at a time, the idea you are least sure of first, and each answer is explained at once:

Practice that adapts to you

Practise this lesson

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

  1. Question 1 of 15 What does var tell the compiler in var shows = 3;?

    Choose one answer.

    Show the answer: Question 1 of 15

    Answer: Give shows the type of its initial value, here int

    var asks the compiler to take the type from the initial value. The variable then has that type for good, as if you had written int shows = 3;.

  2. Question 2 of 15 Which of these lines compiles?

    Choose one answer.

    Show the answer: Question 2 of 15

    Answer: var shows = 3;

    var takes the type from the initial value, so it needs one, and the value needs a type of its own. 3 is an int. var count; has no value, null belongs to no single type, and an array initialiser such as {10, 20} needs a declared array type, as in int[] seats = {10, 20};.

  3. Question 3 of 15 After var names = new ArrayList<String>();, which type does names have?

    Choose one answer.

    Show the answer: Question 3 of 15

    Answer: ArrayList<String>

    var gives the variable the exact type of its initial value, here ArrayList<String>. A later names = new LinkedList<String>(); is refused: LinkedList<String> cannot be converted to ArrayList<String>. Write List<String> names = new ArrayList<>(); when the variable should accept any list.

  4. Question 4 of 15 What is Double.MIN_VALUE?

    Choose one answer.

    Show the answer: Question 4 of 15

    Answer: The smallest double above zero, about 4.9 × 10⁻³²⁴

    For Float and Double, MIN_VALUE is the smallest positive value. The most negative double is -Double.MAX_VALUE. Only for the whole-number types is MIN_VALUE the most negative value.

  5. Question 5 of 15 Which primitive type is unsigned, so that it holds no negative values?

    Choose one answer.

    Show the answer: Question 5 of 15

    Answer: char

    byte, short, int and long are signed, stored in two's complement. char is the only unsigned primitive type, from 0 to 65,535.

  6. Question 6 of 15 A byte has 8 bits and runs from -128 to 127. By the same rule, what is the most negative value of a short, which has 16 bits?

    Type a number.

    Show the answer: Question 6 of 15

    Answer: -32768

    A type of n bits starts at −2ⁿ⁻¹. For a byte that is −2⁷ = −128; for a short it is −2¹⁵ = −32,768, as Short.MIN_VALUE printed in Ranges.java.

  7. Question 7 of 15 Which is the narrowest type that can hold every value from 0 to 255?

    Choose one answer.

    Show the answer: Question 7 of 15

    Answer: short

    A byte is signed and stops at 127. short holds -32,768 to 32,767, so 0 to 255 fits. char also holds 0 to 255, but it is meant for UTF-16 code units, not numbers, and it is no narrower than short.

  8. Question 8 of 15 An app keeps account balances as a whole number of cents, and a balance can reach 50 million in either direction, which is 5,000,000,000 cents. Which type should hold it?

    Choose one answer.

    Show the answer: Question 8 of 15

    Answer: long

    Whole cents are whole numbers, and 5,000,000,000 needs more than an int, so the answer is a long. It holds about ±9.2 × 10¹⁸ cents, and its arithmetic stays exact.

  9. Question 9 of 15 An array holds 10 million sensor readings, each a whole number from 0 to 100. Which is the best reason to store them as byte instead of int?

    Choose one answer.

    Show the answer: Question 9 of 15

    Answer: The array takes about 10 MB instead of about 40 MB

    byte and short save memory and little else, so they pay off in large arrays: 10 million bytes are about 10 MB, 10 million ints about 40 MB.

  10. Question 10 of 15 Which literal is a float?

    Choose one answer.

    Show the answer: Question 10 of 15

    Answer: 19.99f

    Decimal literals are double unless they end in f (or F). d and D also exist, for double, and change nothing.

  11. Question 11 of 15 What is the value of 010 + 0b11 + 0x10?

    Type a number.

    Show the answer: Question 11 of 15

    Answer: 27

    010 is octal, eight; 0b11 is binary, three; 0x10 is hexadecimal, sixteen. 8 + 3 + 16 = 27.

  12. Question 12 of 15 Which declarations does the compiler refuse?

    Choose every answer that is right.

    Show the answer: Question 12 of 15

    Answer:

    • float price = 19.99;
    • long big = 3000000000;
    • byte level = 200;

    3000000000 is an int literal that is too large for an int, even though the variable is a long: it needs an L. 19.99 is a double, which does not go into a float without f. A constant may go into a byte only when it fits, and 200 does not; 120 does.

  13. Question 13 of 15 Inside main, int count; is declared and nothing is assigned yet. What value does count have?

    Choose one answer.

    Show the answer: Question 13 of 15

    Answer: None, and reading it is a compile error

    Local variables have no default value, so int count; leaves count unassigned, and any line that reads it before an assignment on every path does not compile.

  14. Question 14 of 15 What happens when you run this file with java?

    Read the code, then choose one answer.

    void main() {
        int total;
        boolean big = Math.random() > 0.5;
        if (big) total = 100;
        IO.println(total);
    }
    Show the answer: Question 14 of 15

    Answer: It does not compile: variable total might not have been initialized

    A local variable has no default value. The compiler sees a path on which big is false and total is never assigned, so it refuses to read total. Fields, unlike locals, start at 0.

  15. Question 15 of 15 Does this file compile?

    Read the code, then choose one answer.

    void main() {
        int total;
        int items = 3;
        if (items > 0) total = items * 10;
        if (items <= 0) total = 0;
        IO.println(total);
    }
    Show the answer: Question 15 of 15

    Answer: No: variable total might not have been initialized

    Definite assignment follows paths, not meanings. After the first if, total is assigned only on one path; the second if adds an assignment only when its own test is true, so a path with no assignment remains. Write one if with an else, or give total a starting value.

Interview questions

Fresher · What are Java’s eight primitive types, and why is String not one of them?

The primitive types are byte, short, int and long for whole numbers of 8, 16, 32 and 64 bits, float and double for IEEE 754 floating-point numbers of 32 and 64 bits, char for a 16-bit UTF-16 code unit and boolean for true or false. A variable of a primitive type holds the value itself, has a default value when it is a field, and has no methods. String is a class: a String variable holds a reference to an object, which can be null and has methods such as length(). The language gives strings literals and the + operator, but that does not make them primitive.

Key takeaways

  • Every Java variable has a type fixed at compile time; var lets the compiler infer it from the initial value, for local variables only, and the type never changes afterwards.
  • The primitive types are byte, short, int, long (8 to 64 bits, signed), float and double (IEEE 754), char (an unsigned 16-bit UTF-16 code unit) and boolean, with the same ranges on every computer; Float.MIN_VALUE and Double.MIN_VALUE are the smallest positive values, not the most negative ones.
  • Use int for whole numbers and long once a value can pass 2,147,483,647; use double for fractions, but not for money, and byte, short or float only for raw data or large arrays.
  • Whole-number literals are int unless they end in L; decimal literals are double unless they end in f; 0x, 0b and a leading 0 mean hex, binary and octal; underscores only improve readability.
  • Fields start with a default value; a local variable must be assigned on every path before it is read.

Terms in this lesson

definite assignment
The compiler rule that a local variable must be assigned on every path through the code before it is read. Otherwise the file does not compile: variable might not have been initialized.
literal
A value written directly in the code, such as 120, 0xFF, 12_000_000_000L, 19.99f or true. Whole-number literals are int unless they end in L, and decimal literals are double unless they end in f.
local variable type inference
Writing var instead of a type for a local variable with an initial value: the compiler gives the variable the type of that value, so var shows = 3; declares an int. The type is fixed when the file compiles.
primitive type
One of the eight value types built into Java: byte, short, int, long, float, double, char and boolean. A variable of a primitive type holds the value itself and has no methods.
reference type
Every type that is not primitive: classes such as String, interfaces and arrays. A variable of a reference type holds a reference to an object, or null.
two's complement
How Java stores signed whole numbers: the highest bit counts as minus 2 to the power n − 1, so an n-bit type runs from −2ⁿ⁻¹ to 2ⁿ⁻¹ − 1, and the byte 11111111 is -1.

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.