Declaring variables

A variable is declared with its type, a name and usually a starting value. After that the type is fixed: the compiler checks that every value you store fits it (see static typing). Names start with a letter and use camelCase by convention: meterId, pricePerKwh.

void main() {
    String meterId = "E-001";
    int kwh = 350;
    double pricePerKwh = 0.30;
    boolean smartMeter = true;
    char tariffCode = 'D';

    kwh = kwh + 12;              // a variable can change...
    final int vatPercent = 21;   // ...a final one cannot

    IO.println(meterId + " used " + kwh + " kWh");
    IO.println("Cost before VAT: " + kwh * pricePerKwh);
    IO.println("Smart meter: " + smartMeter + ", tariff " + tariffCode);
    IO.println("VAT: " + vatPercent + "%");
}
E-001 used 362 kWh
Cost before VAT: 108.6
Smart meter: true, tariff D
VAT: 21%

The examples on this page are compact source files (see Compact source files) that print with IO.println; inside a classic class, System.out.println prints exactly the same.

Constants with final

final makes a variable a constant: it gets a value once and can never be reassigned. Constants shared by a whole class are written as static final fields in capitals, such as MAX_READINGS. Assigning to a final variable is a compiler error:

void main() {
    final int vatPercent = 21;
    vatPercent = 9;
    IO.println(vatPercent);
}
Main.java:3: error: cannot assign a value to final variable vatPercent

Java also refuses to read a local variable before it has a value: int kwh; IO.println(kwh); gives error: variable kwh might not have been initialized. There are no "undefined" or random values, as in JavaScript or C.

The var keyword

Since Java 10 you may write var instead of the type of a local variable. This is local variable type inference: the compiler works out the type from the starting value and fixes it, exactly as if you had written it (see type inference). var is not a dynamic type.

void main() {
    var meterId = "E-001";            // String
    var kwh = 350;                    // int
    var price = 0.30;                 // double
    var readings = new ArrayList<Integer>();
    readings.add(kwh);

    IO.println(meterId + " " + kwh + " " + price + " " + readings);
    kwh = 351;                        // fine: still an int
    IO.println(kwh);
}
E-001 350 0.3 [350]
351

After var kwh = 350, the line kwh = "351" fails with incompatible types: String cannot be converted to int. var only works for local variables with a starting value: not for fields or method parameters, and not as var x; or var x = null; (the compiler answers cannot infer type for local variable x). Use it where the type is obvious from the right-hand side, as with new ArrayList<Integer>(); write the type where it helps the reader.

Primitive types

Java has exactly eight primitive types: built-in types whose variables hold the value itself, not a reference to an object. Their sizes are the same on every platform, which is part of Java's portability.

Type Size Holds Example
byte 8 bits Whole numbers −128 to 127 Raw bytes from a file or network
short 16 bits Whole numbers −32,768 to 32,767 Rarely used
int 32 bits Whole numbers up to about ±2.1 billion Counts, kWh, ticket numbers
long 64 bits Whole numbers up to about ±9.2 × 1018 Watt-hours over years, timestamps in milliseconds
float 32 bits Floating-point, about 7 significant digits Rarely used; prefer double
double 64 bits Floating-point, about 15–16 significant digits Measurements, temperatures
char 16 bits One UTF-16 code unit (a character) 'D', '€'
boolean — true or false Flags, conditions

The whole-number types store negative numbers in two's complement; float and double are IEEE 754 floating-point numbers. Each type's limits are available as constants:

void main() {
    IO.println(Byte.MIN_VALUE + " to " + Byte.MAX_VALUE);
    IO.println(Short.MIN_VALUE + " to " + Short.MAX_VALUE);
    IO.println(Integer.MIN_VALUE + " to " + Integer.MAX_VALUE);
    IO.println(Long.MIN_VALUE + " to " + Long.MAX_VALUE);
    IO.println(Float.MAX_VALUE + " and " + Double.MAX_VALUE);
    IO.println((int) Character.MAX_VALUE);
}
-128 to 127
-32768 to 32767
-2147483648 to 2147483647
-9223372036854775808 to 9223372036854775807
3.4028235E38 and 1.7976931348623157E308
65535

Large and small double values print in scientific notation: 1.7976931348623157E308 means 1.797… × 10308. Everything that is not one of the eight primitives is a reference type: String, arrays, List, your own classes and records. A variable of a reference type holds a reference to an object on the heap, or null; a primitive variable can never be null.

Literals

A literal is a value written directly in the code. A whole number is an int unless it ends in L (long); a number with a decimal point is a double unless it ends in f (float). Underscores make long numbers readable.

void main() {
    long customersEver = 3_000_000_000L;   // L: a long literal
    int hex = 0x1F;
    int binary = 0b1010;
    int octal = 017;                        // leading zero: octal!
    float tariff = 0.27f;                   // f: a float literal
    double usage = 1.25e3;                  // 1.25 × 10³
    char euro = '€';

    IO.println(customersEver);
    IO.println(hex + " " + binary + " " + octal);
    IO.println(tariff + " " + usage + " " + euro);
}
3000000000
31 10 15
0.27 1250.0 €

Without the L, long big = 3000000000; does not compile (error: integer number too large): the literal itself must fit in an int before it is stored. The leading-zero rule is a classic trap: 017 is octal, so a ticket number or postal code with a leading zero must be a string, not a number.

Wrapper classes and autoboxing

Collections such as List can only hold objects, not primitive values. For that reason every primitive type has a wrapper class: a class whose objects hold one primitive value. They are Byte, Short, Integer, Long, Float, Double, Character and Boolean. The wrapper classes also hold useful static methods, such as Integer.parseInt.

Since Java 5 the compiler converts between the two forms for you. Autoboxing wraps an int into an Integer where an object is needed, and unboxing takes the value out again where a primitive is needed:

void main() {
    int count = 3;
    Integer boxed = count;            // autoboxing: int -> Integer
    int unboxed = boxed + 1;          // unboxing: Integer -> int
    List<Integer> readings = new ArrayList<>();
    readings.add(342);                // boxed automatically

    IO.println(boxed + " " + unboxed + " " + readings);
    IO.println(Integer.parseInt("42") + Double.parseDouble("1.5"));
    IO.println(Integer.toBinaryString(10) + " " + Integer.MAX_VALUE);
}
3 4 [342]
43.5
1010 2147483647

That is why you write List<Integer> and never List<int>, which does not compile. Boxing is convenient, but a wrapper is an object, and objects behave differently from values in two ways that cause real bugs.

Comparing wrapper objects

For objects, == asks "is this the same object?", not "is this the same value?". Java keeps a cache of Integer objects for the values −128 to 127, so small numbers happen to be the same object and larger ones are not:

void main() {
    Integer meterA = 127, meterB = 127;
    Integer meterC = 128, meterD = 128;

    IO.println(meterA == meterB);
    IO.println(meterC == meterD);
    IO.println(meterC.equals(meterD));
}
true
false
true

Code that compares readings with == therefore passes every test with small numbers and fails in production with large ones. Compare wrapper objects with equals, or unbox them to int first. The same rule applies to strings, see comparing strings.

null and unboxing

A wrapper variable can be null; an int cannot. Unboxing a null throws a NullPointerException, often in a line that does not look dangerous at all:

void main() {
    Map<String, Integer> usage = new HashMap<>();
    usage.put("E-001", 350);
    int kwh = usage.get("E-002");     // no such meter: get returns null
    IO.println(kwh);
}
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null

The message explains what happened: to unbox, Java called intValue() on the result of Map.get, which was null. Use usage.getOrDefault("E-002", 0) or check for null first; see Null, Optional and NullPointerException.

Conversions and casts

A conversion to a type that can hold every value of the old one is a widening conversion and happens automatically: int to long, int to double. The opposite direction is a narrowing conversion: it can lose information, so Java demands an explicit cast, the target type in parentheses.

void main() {
    int kwh = 350;
    long total = kwh;                  // widening: always safe
    double precise = kwh;              // int -> double: also automatic
    IO.println(total + " " + precise);

    double average = 11.78;
    int rounded = (int) average;       // narrowing: needs a cast, drops the fraction
    IO.println(rounded);
    IO.println((int) -3.99);
    IO.println(Math.round(average));
    IO.println((byte) 200);
    IO.println((int) 3e10);
}
350 350.0
11
-3
12
-56
2147483647

A cast from double to int truncates toward zero; it does not round. Use Math.round to round. Casting a whole number to a smaller type keeps only the low bits ((byte) 200 becomes -56), while a double that is too large for an int is clamped to Integer.MAX_VALUE. Without the cast, the compiler refuses:

void main() {
    double average = 11.78;
    int rounded = average;
    IO.println(rounded);
}
Main.java:3: error: incompatible types: possible lossy conversion from double to int

Text is not converted by casting at all: (int) "42" does not compile. Use Integer.parseInt, Double.parseDouble and friends, which throw a NumberFormatException for text that is not a number (see Converting input to numbers). The other way round, String.valueOf(350) or simply "" + 350 gives text.

Arithmetic operators

Java has the usual operators: +, -, *, /, and % for the remainder. The type of the result depends on the types of the operands: two int values give an int, and as soon as one side is a double the result is a double.

Integer division

Dividing two whole numbers is integer division: the result is truncated toward zero, exactly as in C#. Python's // rounds down instead, which differs for negative numbers; Java offers that behavior as Math.floorDiv and Math.floorMod.

void main() {
    IO.println(7 / 2);
    IO.println(7 % 2);
    IO.println(7 / 2.0);
    IO.println(-7 / 2);
    IO.println(-7 % 2);
    IO.println(Math.floorDiv(-7, 2) + " " + Math.floorMod(-7, 2));

    int day1 = 350, day2 = 351;
    IO.println((day1 + day2) / 2);
    IO.println((day1 + day2) / 2.0);
}
3
1
3.5
-3
-1
-4 1
350
350.5

The average of 350 and 351 comes out as 350: a silent loss of precision, not an error. Write 2.0, or cast one operand to double, when you want a fraction. Dividing a whole number by zero throws java.lang.ArithmeticException: / by zero; dividing a double by zero does not, as the floating-point section shows.

Increment and compound assignment

kwh += 12 is short for kwh = kwh + 12, and likewise -=, *=, /= and %=. ++ and -- add or subtract one. Placed after the variable (k++) the expression gives the old value; placed before (++k) it gives the new value.

void main() {
    int kwh = 350;
    kwh += 12;
    kwh -= 2;
    kwh *= 2;
    IO.println(kwh);

    int k = 5;
    IO.println(k++ + " " + k);
    IO.println(++k + " " + k);

    int i = 5;
    i += 2.7;              // compiles: the result is cast back to int
    IO.println(i);
}
720
5 6
7 7
7

Use ++ as a statement on its own line (count++;); inside larger expressions it is hard to read. The last lines show a trap: a compound assignment includes a hidden cast to the variable's type, so i += 2.7 quietly drops the fraction, while the long form is refused:

void main() {
    int i = 5;
    i = i + 2.7;
    IO.println(i);
}
Main.java:3: error: incompatible types: possible lossy conversion from double to int

Overflow

Each whole-number type has a fixed range, and Java does not warn you when a result leaves it: the value wraps around to the other end of the range (an overflow). There is no runtime error, just a wrong number.

void main() {
    int max = Integer.MAX_VALUE;
    IO.println(max + 1);

    int whPerYear = 365 * 24 * 1000 * 1000;      // looks fine...
    long whPerYearLong = 365L * 24 * 1000 * 1000;
    IO.println(whPerYear);
    IO.println(whPerYearLong);

    IO.println(Math.addExact(max, 1));
}
-2147483648
170065408
8760000000
Exception in thread "main" java.lang.ArithmeticException: integer overflow

The second line is the dangerous case: the real value, 8.76 billion watt-hours, needs a long, but the calculation is done in int because all four literals are int. Making the first operand a long (365L) makes the whole calculation long. Where an overflow must never pass unnoticed, Math.addExact, Math.multiplyExact and friends throw an exception instead.

Floating-point surprises

double stores numbers in binary, and most decimal fractions, such as 0.1, have no exact binary form (see why 0.1 + 0.2 is not 0.3). Small errors appear, and comparisons with == fail. Division by zero gives special values instead of an exception:

void main() {
    IO.println(0.1 + 0.2);
    IO.println(0.1 + 0.2 == 0.3);
    IO.println(Math.abs((0.1 + 0.2) - 0.3) < 1e-9);
    IO.println(1.0 / 0);
    IO.println(0.0 / 0);
    IO.println(Double.NaN == Double.NaN);
    IO.println(Double.isNaN(0.0 / 0));
    IO.println(Math.round(2.5) + " " + Math.round(-2.5));
}
0.30000000000000004
false
true
Infinity
NaN
false
true
3 -2

Compare measured values with a tolerance, as in the third line. NaN ("not a number") is not even equal to itself; test for it with Double.isNaN. And note Math.round: it rounds halves up toward positive infinity, so 2.5 becomes 3 but -2.5 becomes -2. (C#'s Math.Round and Python's round round halves to the nearest even number instead.) double is fine for temperatures and measurements; for money it is the wrong type.

BigDecimal for money

BigDecimal is a class in java.math for exact decimal numbers of any size: a whole number of digits plus a scale, the number of digits after the decimal point. It is Java's type for money. Because it is a class, you calculate with methods instead of operators:

void main() {
    BigDecimal tariff = new BigDecimal("0.30");        // euros per kWh
    BigDecimal kwh = new BigDecimal("350");
    BigDecimal net = tariff.multiply(kwh);
    BigDecimal vat = net.multiply(new BigDecimal("0.21")).setScale(2, RoundingMode.HALF_UP);
    BigDecimal total = net.add(vat);

    IO.println("Net:   " + net);
    IO.println("VAT:   " + vat);
    IO.println("Total: " + total);

    IO.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
    IO.println(new BigDecimal(0.1));
    IO.println(BigDecimal.valueOf(0.1));
}
Net:   105.00
VAT:   22.05
Total: 127.05
0.3
0.1000000000000000055511151231257827021181583404541015625
0.1

Three rules make BigDecimal work for you:

  • Create it from a string, as in new BigDecimal("0.30"), or with BigDecimal.valueOf(0.1). The constructor that takes a double faithfully copies the double's binary error, as the second-to-last line shows.
  • Round explicitly with setScale(2, RoundingMode.HALF_UP) (or HALF_EVEN, "banker's rounding", if your rules demand it). The scale of a product is the sum of the scales: 0.30 × 350 gives 105.00.
  • Give division a scale and rounding mode. A division without them must be exact, and fails otherwise.
void main() {
    BigDecimal yearly = new BigDecimal("1000.00");
    BigDecimal monthly = yearly.divide(new BigDecimal("12"));
    IO.println(monthly);
}
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
void main() {
    BigDecimal yearly = new BigDecimal("1000.00");
    BigDecimal monthly = yearly.divide(new BigDecimal("12"), 2, RoundingMode.HALF_UP);
    IO.println(monthly);

    BigDecimal a = new BigDecimal("2.50");
    BigDecimal b = new BigDecimal("2.5");
    IO.println(a.equals(b));
    IO.println(a.compareTo(b) == 0);
}
83.33
false
true

The last two lines show the final trap: equals also compares the scale, so 2.50 and 2.5 are not "equal". Compare amounts with compareTo, which returns 0 for the same value. Formatting amounts as € 127,05 is covered in Strings and Text.

Comparison and logical operators

Comparisons (==, !=, <, <=, >, >=) produce a boolean. The logical operators combine them: && (and), || (or) and ! (not). && and || use short-circuit evaluation: the right side is skipped when the left side already decides the result.

void main() {
    int kwh = 1250;
    boolean highUse = kwh > 1000;
    boolean smartMeter = false;

    IO.println(highUse && !smartMeter);
    IO.println(highUse || smartMeter);
    IO.println(kwh == 1250 && kwh != 0);
    IO.println(10 == 10.0);

    String note = null;
    IO.println(note != null && note.length() > 0);   // && stops early: no crash
    IO.println(1 + 2 + " kWh");
    IO.println("kWh: " + 1 + 2);
    IO.println('A' + 1);
}
true
true
true
true
false
3 kWh
kWh: 12
66

The last three lines show how + depends on its operands, evaluated from left to right: number plus number is addition, but once a string is involved everything after it is joined as text. A char is a number underneath, so 'A' + 1 is 66 (its Unicode code point plus one). Java has no "truthiness": a condition must be a real boolean.

void main() {
    int kwh = 350;
    if (kwh) {
        IO.println("Usage recorded");
    }
}
Main.java:3: error: incompatible types: int cannot be converted to boolean

In Python or JavaScript if kwh would test for "not zero"; in Java you write what you mean: if (kwh > 0). The single & and | also work on booleans but always evaluate both sides; on whole numbers they are bitwise operators, together with ^, ~, <<, >> and >>>.

Operator precedence

As in mathematics, *, / and % bind more strongly than + and -; comparisons come after arithmetic, then &&, then ||, and assignment comes last. Operators of equal rank are evaluated from left to right.

void main() {
    char grade = 'B';
    IO.println(grade + 1);
    IO.println((char) (grade + 1));
    IO.println('7' - '0');
    IO.println(2 + 3 * 4);
    IO.println((2 + 3) * 4);
    IO.println(10 - 4 - 3);
    IO.println(100 / 10 / 5);
}
67
C
7
14
20
3
2

'7' - '0' turns a digit character into its number value, a common trick when parsing by hand. When in doubt, add parentheses: they cost nothing and make the intent clear. The full order is the same as in C# and very close to Python's (see operator precedence).