Java Data Types You Need To Know

Java Data Types

While working with programming languages, you will often come across something called "data types". These are like labels that tell the computer what kind of data a piece of memory will be holding. In this article, we will talk about Java data types and their categories, and then look at wrapper classes, autoboxing, and unboxing, which connect the two categories.

Data types form the basics of the Java programming language. They are like the building blocks of a program, and you need them to understand concepts like data handling and memory allocation.

We compiled and ran every example on Java 21, and the output shown under each one is the real output. Are you ready to get started? Well then, let us dive in!

TL;DR: Java Data Types

AspectSummary
DefinitionA data type defines what kind of values a variable can hold and how the computer should interpret them.
CategoriesPrimitive data types store actual values. Non-primitive (reference) data types store references to objects.
Primitive Data Typesbyte, short, int, long, float, double, char, and boolean.
Non-Primitive Data TypesArrays, Strings, Classes, and Interfaces.
Wrapper ClassesEach primitive has an object version, like Integer for int. Java converts between them automatically (autoboxing and unboxing).
Best PracticesUse int for whole numbers, long for larger values, double for decimals, boolean for true and false, char for single characters, and String for text.
Common ErrorsInteger overflow, loss of precision in type conversion, NullPointerException for null references (including when unboxing), comparing wrapper objects with ==, and char/Unicode confusion.

What Are The Data Types in Java? Why Do We Use Data Types in Java?

Data types in programming tell us what kind of value we are dealing with. For example, are we working with numbers or text? Is it a single letter or a whole sentence? The computer gets to know this with the help of data types.

So, does every Java variable have a data type? The answer is yes. Every variable in Java must have a data type.

A variable's type is needed in its declaration so the compiler knows how to handle the data it stores. Java is a statically typed language, which means a variable's type is fixed when you write the code, not when the program runs. This way, the compiler knows how much space to set aside and can catch type mistakes before the program ever runs.

Since Java 10, you can write var instead of the type name for local variables. The compiler works out the type from the value, but the variable still has a fixed type:

public class VarExample {
    public static void main(String[] args) {
        var count = 10;          // the compiler decides this is an int
        var name = "Priya";      // and this is a String
        // count = "ten";        // still an error: count is an int forever
        System.out.println(count + " " + name);
    }
}
10 Priya

So, how are data types classified in Java? Let us see!

Two Main Types Of Data Types In Java

In Java, data types are mainly classified into two types:

Data Types In Java

Now, let us understand what primitive and non-primitive data types are.

Primitive Data Types:

These are the basic data types that store the actual value. Each primitive variable holds its own copy of the value, so changing one never changes another. Where they live in memory depends on where you declare them: a primitive local variable lives on the stack, while a primitive field inside an object lives on the heap with that object.

Non-Primitive (Reference) Data Types:

Reference data types store references to objects, meaning they point to the address of the actual data rather than storing it directly. Examples include classes (like String), interfaces, arrays, and enums. In this discussion, we will explore both primitive and non-primitive data types in Java.

Java Homework Help

Primitive Data Types Supported In Java

The primitive data types are the basic, foundational data types in Java programming. Let us discuss the eight primitive data types used in Java. Each one is declared using a reserved keyword. Here is a quick summary before we look at each one with its size, uses, and default value:

TypeSizeRangeDefault valueExample
byte8 bits (1 byte)-128 to 1270byte b = -10;
short16 bits (2 bytes)-32,768 to 32,7670short s = 20000;
int32 bits (4 bytes)-2,147,483,648 to 2,147,483,6470int i = 8000000;
long64 bits (8 bytes)about -9.2 x 1018 to 9.2 x 10180Llong l = 8200000000L;
float32 bits (4 bytes)about ±1.4e-45 to ±3.4e+38, 6 to 7 digits of precision0.0ffloat f = 150.25f;
double64 bits (8 bytes)about ±4.9e-324 to ±1.8e+308, 15 to 16 digits of precision0.0ddouble d = 9.81;
char16 bits (2 bytes)0 to 65,535 (a Unicode character)'\u0000'char c = 'A';
booleanNot precisely defined (depends on the JVM)true or falsefalseboolean ok = true;

1. byte data type

The first primitive data type is byte. It is used to represent small signed integer values using 8 bits (1 byte).

Size: A byte occupies 8 bits in memory.
Range: A byte can hold values from -128 (minimum value) to 127 (maximum value).
Use: Byte is typically used when saving memory is important, for example in large arrays, and in low-level work like reading and writing binary files and networking.
Default Value: 0

Example code for the byte data type:

public class ByteExample {
    public static void main(String[] args) {
        byte temperature = -10;
        System.out.println(temperature);
    }
}

Output:

-10

In this example, the temperature variable is declared as a byte and assigned the value -10. Since -10 falls within the range of a byte, this code is valid and demonstrates the use of the byte data type.

2. short data type

The short data type is a 16-bit signed two's complement integer. Its details are given below.

Size: A short takes up 16 bits (2 bytes) in memory.
Range: minimum value of -32,768 to maximum value of 32,767
Use: Because it is bigger than a byte, it can store values outside the -128 to 127 range, but still within a limited range of numbers.
Default Value: 0

Example Java code for the short data type:

public class ShortExample {
    public static void main(String[] args) {
        short studentsCount = 20000;
        System.out.println("Student count: " + studentsCount);
    }
}

Output:

Student count: 20000

In this example, the studentsCount variable is declared as short because the value 20000 falls within its range.

3. int data type

The int type in Java is a 32-bit signed two's complement integer. It is used to store whole numbers (integral values without a decimal point). Java's int is always signed. There is no separate unsigned int type, although since Java 8, the Integer class has helper methods like Integer.toUnsignedString() for the rare cases where you need to treat the bits as unsigned.

Size: The int data type takes up 32 bits (4 bytes) in memory.
Range: -231 to 231 - 1, which is -2,147,483,648 to 2,147,483,647.
Use: It is the most commonly used whole-number type in Java, because its range is big enough for most everyday calculations.
Default Value: 0

Example Java code for the int data type:

public class IntExample {
    public static void main(String[] args) {
        int bankBalance = 8000000;
        System.out.println("Balance in bank: " + bankBalance);
    }
}

Output:

Balance in bank: 8000000

In this code, an integer variable bankBalance is declared and assigned the value 8000000 (8 million). Since 8 million is well within the int range of about 2.1 billion, int is a suitable data type to hold it.

Watch out for integer overflow. If a calculation goes past the maximum value, an int does not give an error. It silently wraps around to the negative end of the range, so the maximum value plus 1 becomes the most negative int:

public class OverflowExample {
    public static void main(String[] args) {
        int max = Integer.MAX_VALUE;           // 2,147,483,647
        System.out.println(max);
        System.out.println(max + 1);           // wraps around to the minimum

        long safe = (long) max + 1;            // do the math in long instead
        System.out.println(safe);
    }
}

Output:

2147483647
-2147483648
2147483648

Casting one side to long before adding makes Java do the math in 64 bits, which gives the right answer.

4. long data type

This data type is a 64-bit signed two's complement integer. It is used to store very large whole numbers. Find its details below!

Size: The long data type takes up 64 bits (8 bytes) in memory.
Range: -263 to 263 - 1, which is about -9.2 quintillion to 9.2 quintillion.
Use: long is used when dealing with numbers too big for an int, like timestamps in milliseconds or large IDs. Remember that it takes twice the memory of an int.
Default Value: 0L (L is the suffix)

Example Java code for the long data type:

public class LongExample {
    public static void main(String[] args) {
        long worldPopulation = 8200000000L;   // about 8.2 billion
        System.out.println("World population: " + worldPopulation);
    }
}

Output:

World population: 8200000000

In this code, worldPopulation is declared as long and assigned the value 8200000000L. The L at the end tells Java this is a long literal. Without it, the compiler reports "integer number too large," because whole-number literals are treated as int by default.

Like int, long is always signed in Java. Since Java 8, the Long class has helper methods like Long.toUnsignedString() and Long.divideUnsigned() that treat the same 64 bits as an unsigned value from 0 to 264 - 1, but the type itself does not change.

5. float data type

The float data type is a 32-bit single-precision IEEE 754 floating-point data type. As the name suggests, it is used to store floating-point (decimal) numbers. However, it has limited precision, about 6 to 7 significant digits, and it rounds values internally, which makes it unsuitable when precision is important. There is a real difference between the float and double data types in Java.

Size: The float data type takes up 32 bits (4 bytes) in memory.
Range: approximately ±1.4e-45 to ±3.4e+38.
Use: Use float when you need to store decimal numbers and are willing to trade some precision for memory, for example in large arrays of sensor readings or graphics data.
Default Value: 0.0f (f is the suffix)

Example Java code for the float data type:

public class FloatExample {
    public static void main(String[] args) {
        float distance = 150.25f;
        float elevation = -4.3e2f; // scientific notation: -4.3 x 10^2

        System.out.println("Distance: " + distance);
        System.out.println("Elevation: " + elevation);
    }
}

Output:

Distance: 150.25
Elevation: -430.0

In the above code, the distance variable shows a float with the suffix f, and the elevation variable shows the scientific way of writing a float value (-4.3 x 102, which is -430). If you forget the f and write float price = 150.25;, the code will not compile, and you get "incompatible types: possible lossy conversion from double to float." Our guide to scientific notation in Java explains that format in detail.

6. double data type

The double data type is a 64-bit double-precision floating-point type. It can store much larger decimal values and gives more accuracy than float, about 15 to 16 significant digits. By default, if a decimal literal does not end with the suffix f (or F), Java treats it as a double.

Size: 64 bits (8 bytes)
Range: approximately ±4.9e-324 to ±1.8e+308.
Use: double is the default choice for decimal numbers in science, math, and most everyday calculations. But even a double has small rounding errors, so it should not be used for exact values like money. For money, use BigDecimal, or store amounts in cents as a long.
Default Value: 0.0d

Example Java code for the double data type:

public class DoubleExample {
    public static void main(String[] args) {
        double gravity = 9.81;
        double pi = 3.141592653589793;

        System.out.println("Gravity: " + gravity);
        System.out.println("Pi: " + pi);
        System.out.println("0.1 + 0.2 = " + (0.1 + 0.2));
    }
}

Output:

Gravity: 9.81
Pi: 3.141592653589793
0.1 + 0.2 = 0.30000000000000004

In the code, we store a short value (9.81) and a long one (pi to 15 decimal places) in two double variables, and Java prints both exactly. The last line shows the rounding problem in action: 0.1 and 0.2 cannot be stored exactly in binary, so their sum is a tiny bit off. That is why double should never be used for exact amounts like a bank balance.

7. char data type

The char data type is used to represent a single character. It occupies 16 bits of space in memory. It stores characters using Unicode, which lets it handle letters, digits, symbols, punctuation, and special characters like a newline (written '\n'). Behind the scenes, a char is a number from 0 to 65,535, which is why you can do math with it. (Some characters, like most emojis, need two char values, so they are usually stored in a String.)

Use: Helpful in processing strings, handling text input, and performing character-based operations.
Default Value: '\u0000' (0)

Example Java code for the char data type:

public class CharExample {
    public static void main(String[] args) {
        char letter = 'A';
        char digit = '5';
        char percent = '%';
        char e = '\u0065';   // the Unicode code for 'e'

        System.out.println("Letter: " + letter);
        System.out.println("Digit: " + digit);
        System.out.println("Symbol: " + percent);
        System.out.println("Small e: " + e);
        System.out.println("Next letter: " + (char) (letter + 1));
    }
}

Output:

Letter: A
Digit: 5
Symbol: %
Small e: e
Next letter: B

The output displays the characters 'A', '5', and '%' as expected, and it shows the lowercase letter 'e' for the e variable, which was assigned the Unicode code point \u0065. The last line adds 1 to 'A' (code 65) and turns the result, 66, back into a character: 'B'.

8. boolean data type

The boolean data type has only two values: true and false. Its exact size in memory is not defined by the Java specification and depends on the JVM.

Default value: false
Use: It is mainly used in conditional statements and expressions to decide whether certain conditions are met. It is the result of comparison operators (>=, <=, ==) and works with the logical operators !, ||, and &&.

Example Java code for the boolean data type:

public class BooleanExample {
    public static void main(String[] args) {
        boolean isRaining = true;
        boolean hasUmbrella = false;
        boolean stayHome = isRaining && !hasUmbrella;

        System.out.println("Should I stay home? " + stayHome);
    }
}

Output:

Should I stay home? true

In this example, the code uses boolean variables to represent whether it is raining and whether the person has an umbrella. The program then uses the logical operators && and ! to decide whether the person should stay home.

Where Do the Default Values Apply?

The default values listed above only apply to fields (variables declared inside a class but outside any method) and array elements. A local variable inside a method gets no default value. If you use one before giving it a value, the code will not compile, and you get an error like "variable count might not have been initialized." Here are the defaults for fields:

public class DefaultValues {
    static byte b;
    static short s;
    static int i;
    static long l;
    static float f;
    static double d;
    static char c;
    static boolean flag;
    static String text;

    public static void main(String[] args) {
        System.out.println(b + " " + s + " " + i + " " + l + " " + f + " " + d + " " + (int) c + " " + flag + " " + text);
    }
}

Output:

0 0 0 0 0.0 0.0 0 false null

The char default, '\u0000', is printed as its number, 0, and reference types like String default to null.

Non-Primitive Data Types Supported In Java

The non-primitive data types (reference types) in Java are used to refer to objects. In this section, we will discuss the four non-primitive data types you will use most often in Java. (Enums and records are reference types too, but they are special kinds of classes.) Let us move forward!

1. Arrays

An array data type stores elements of the same data type. For instance, the marks a student got in different subjects can be stored together in an array, like int[] marks = {85, 92, 78};.

You declare an array with the element type followed by square brackets. The elements can be primitives, like int[], or objects, like String[]. An array has a fixed length that you set when you create it, and the array variable holds a reference to the array object. Arrays can be used as method parameters, return values, and local variables.

To know how to work with an array and its methods in Java, you can check out our article on Java array methods.

2. Strings

A string is a sequence of characters. String is a class, so it is non-primitive, but it is built into Java and is one of the most used data types in the language.

String literals are written using double quotes, and string objects are immutable, which means they cannot be changed after they are created. In Java, strings are not terminated with a null character, unlike in C. Below is a coding example for using strings.

Example Java code for the String data type:

public class StringExample {
    public static void main(String[] args) {
        String st = "Welcome to CodingZap!";
        System.out.println(st);
        System.out.println("Length: " + st.length());
    }
}

Output:

Welcome to CodingZap!
Length: 21

So here, we have a series of characters enclosed in double quotes. Such a series forms a character string. We have given the name st to the string variable. Because a String is an object, it has methods too, like length(), which counts the char values in the string (usually the same as the number of characters).

3. Class

A class is a blueprint for creating objects. It can have variables of different data types and methods that define how its objects behave.

To declare a class in Java, you simply use the class keyword followed by the class name you want. Let us see an example class below.

Example Java code for a class:

public class ClassExample {
    int num1;
    int num2;

    public void addition() {
        System.out.println("The sum of two numbers is: " + (num1 + num2));
    }

    public static void main(String[] args) {
        ClassExample obj = new ClassExample();
        obj.num1 = 2;
        obj.num2 = 3;
        obj.addition();
    }
}

Output:

The sum of two numbers is: 5

In the above code, we have a class ClassExample that adds two integer instance variables (fields), num1 and num2. We have an object obj of the class ClassExample that has num1 and num2 as its attributes.

4. Interface

Just like a class is a blueprint of an object, an interface is a blueprint for a class. It lists methods that a class promises to provide. An interface's methods are abstract (they have no body) unless you mark them otherwise. Since Java 8, an interface can also have default and static methods with a body. The following example will help you understand it.

Example Java code for an interface:

interface Printable {
    void print();
}

public class InterfaceExample implements Printable {
    public void print() {
        System.out.println("Welcome to CodingZap!");
    }

    public static void main(String[] args) {
        InterfaceExample obj = new InterfaceExample();
        obj.print();
    }
}

Output:

Welcome to CodingZap!

Alright! So, here we need to save this program in a file named InterfaceExample.java. We have created an interface Printable with the method print(), and the class InterfaceExample provides the body for that method.

Java is an object-oriented programming language, and it uses classes and objects throughout the code you write. You can build them well by choosing the correct Java data types for the values they store.

Primitive vs Non-Primitive Data Types in Java

Here is a side-by-side look at the two categories:

PrimitiveNon-primitive (reference)
What it storesThe actual valueA reference to an object
Who defines itJava (exactly 8 types)Java has some built in, like String, and you can create your own classes
Can it be null?NoYes
Default value (fields)0, 0.0, false, or '\u0000'null
SizeFixed for each type (except boolean, which depends on the JVM)Depends on the object
MethodsNoneCan have methods, like st.length()
NamingLowercase keywords, like int and doubleClass names start with a capital letter by convention, like String and Integer
Examplesint, char, booleanString, arrays, classes, interfaces

So what happens when you need a primitive value but Java wants an object? That is where wrapper classes come in.

Wrapper Classes: Autoboxing and Unboxing in Java

Java has primitive data types like int, float, double, and boolean. However, to fully use the object-oriented features of the language, primitives are not enough. For example, collections like ArrayList can only hold objects. To solve this, Java gives every primitive type a matching wrapper class:

Primitive typeWrapper class
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

Before Java 5, you had to convert by hand, with code like new Integer(10) to wrap a value and number.intValue() to unwrap it. (Today you would write Integer.valueOf(10) instead, because the new Integer(...) constructor is deprecated.) Java 5 introduced autoboxing and unboxing, which do these conversions for you.

This reduced the errors that came from manual conversion and made code shorter and easier to read. Let us look at each one with examples.

Autoboxing In Java

Java autoboxing automatically converts a primitive data type to its matching wrapper class. This happens when you assign a primitive value to a wrapper variable, or pass it to a method that expects the wrapper object.

The code given below shows this with the int primitive type and its wrapper class Integer.

Code Example:

public class AutoboxingDemo {
    public static void main(String[] args) {
        // A value in the primitive type int
        int value = 10;
        // Autoboxing: int -> Integer
        Integer objectValue = value;

        System.out.println("Primitive int has value " + value);
        System.out.println("Autoboxed Integer has value " + objectValue);
    }
}

Output:

Primitive int has value 10
Autoboxed Integer has value 10

Explanation Of The Code:

  • First, we declared a primitive int and assigned a value to it.
  • Next, we created an Integer variable and assigned the primitive int value to it.
  • The compiler understands this and performs the conversion for us. Behind the scenes, it turns the line into Integer.valueOf(value).
  • That is why there are no errors, and the output shows the correct value (10) for both.

Unboxing In Java

Java unboxing automatically converts a wrapper class object to its matching primitive data type. This happens when you assign a wrapper object to a primitive variable, pass it to a method that expects the primitive, or use it in math.

The code given below converts an Integer object back to its matching int primitive type.

Code Example:

public class UnboxingDemo {
    public static void main(String[] args) {
        // A value in the Integer wrapper class
        Integer objectValue = 20;
        // Unboxing: Integer -> int
        int value = objectValue;

        System.out.println("Wrapper Integer has value " + objectValue);
        System.out.println("Unboxed int has value " + value);
    }
}

Output:

Wrapper Integer has value 20
Unboxed int has value 20

Explanation Of The Code:

  • First, we declared an Integer object and assigned it a value.
  • Then we declared a primitive int and assigned the Integer object to it, which performs unboxing.
  • The compiler turns this into objectValue.intValue() for us, so we get no errors.

Unboxing is the simplest way to turn an Integer into an int. If you are starting from a general Object or a String, our guide on ways to convert an object to int in Java covers the other options.

Using Autoboxing And Unboxing With Collections

Java collections and autoboxing go hand in hand. Collections like lists, sets, and maps work with objects, not primitive data types. You cannot write List<int>, so you use List<Integer> instead. Thanks to autoboxing, you can still add plain int values without converting them by hand.

Below is a Java program that shows how autoboxing stores primitive values in a list.

Code Example:

import java.util.ArrayList;
import java.util.List;

public class AutoboxingWithCollections {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(5);  // Autoboxing: int -> Integer
        numbers.add(10);
        numbers.add(15);
        numbers.add(20);
        System.out.println("Numbers successfully added!");

        int firstNumber = numbers.get(0); // Unboxing: Integer -> int
        System.out.println("First number in list: " + firstNumber);
    }
}

Output:

Numbers successfully added!
First number in list: 5

Explanation Of The Code:

  • In the above program, we have a List of integers called numbers.
  • We add elements to the list by simply using the add() method with plain int values. This works because of autoboxing.
  • When we read an element back with get(), we can store it in a primitive int with the help of unboxing.

Method Overloading And Autoboxing

Method overloading happens when a class has two or more methods with the same name but different parameter types. Autoboxing affects which of these methods Java picks when some take primitive types and others take wrapper class objects.

Have a look at the coding example below to learn about it.

Code Example:

public class MethodOverloading {
    // with primitive int
    static void display(int num) {
        System.out.println("Primitive int: " + num);
    }

    // with wrapper class Integer
    static void display(Integer num) {
        System.out.println("Wrapper Integer: " + num);
    }

    public static void main(String[] args) {
        int value = 100;
        Integer objectValue = 200;

        display(value);        // calls display(int)
        display(objectValue);  // calls display(Integer)
    }
}

Output:

Primitive int: 100
Wrapper Integer: 200

Explanation Of The Code:

  • In the above code, we have two methods named display().
  • One takes an int as its parameter, and the other takes an Integer object.
  • In the main method, we have two variables: one int and one Integer.
  • The int variable calls the first method, display(int), and the Integer variable calls the second one, display(Integer). Each finds an exact match, so no boxing is needed.

But what if there is no exact match? Java follows a rule here: it first looks for a method it can call without boxing, including widening, like turning an int into a long. It only tries autoboxing if that fails. This rule kept code written before Java 5 working the same way. Here it is in action:

public class WideningVsBoxing {
    static void show(long num) {
        System.out.println("long version: " + num);
    }

    static void show(Integer num) {
        System.out.println("Integer version: " + num);
    }

    public static void main(String[] args) {
        int value = 5;
        show(value);   // widening to long beats autoboxing to Integer
    }
}

Output:

long version: 5

You might expect show(Integer) to be called, because Integer is the wrapper for int. But widening int to long wins over autoboxing int to Integer, so Java calls show(long). This is a favorite trick question in Java exams and interviews.

Performance Considerations Of Autoboxing And Unboxing

While autoboxing makes common operations easier and simplifies the conversion between data types, too much of it can hurt the performance of a Java program.

Performance considerations of autoboxing and unboxing in Java

Here are the main factors to keep in mind:

  • Memory Overhead: A primitive int takes 4 bytes. An Integer object usually takes 16 bytes on a typical 64-bit JVM, plus the reference that points to it. So a list of a million Integer objects uses several times more memory than an int[] array of the same size.
  • Autoboxing/Unboxing in loops: Each time a value is boxed, Java may create a new object. (For Integer and Long, only values from -128 to 127 are reused from a cache. Double values are never cached.) In a loop that runs millions of times, all those extra objects slow the program down.
  • Garbage Collection Overhead: All those short-lived objects have to be cleaned up by the garbage collector. In large, high-performance applications, this extra work can cause slowdowns and delays.

To see the cost for yourself, the program below adds up the numbers from 0 to 9,999,999 twice. The only difference is that the first loop uses the wrapper type Long, and the second uses the primitive long. We run both loops three times and print the last run, so the JVM has time to warm up.

public class BoxingSpeed {
    public static void main(String[] args) {
        // Warm up the JVM so both loops are measured fairly
        for (int run = 0; run < 3; run++) {
            long start = System.nanoTime();
            Long boxedSum = 0L;
            for (int i = 0; i < 10_000_000; i++) {
                boxedSum += i;           // unbox, add, box again every time
            }
            long boxedMs = (System.nanoTime() - start) / 1_000_000;

            start = System.nanoTime();
            long primitiveSum = 0L;
            for (int i = 0; i < 10_000_000; i++) {
                primitiveSum += i;       // plain math, no objects
            }
            long primitiveMs = (System.nanoTime() - start) / 1_000_000;

            if (run == 2) {
                System.out.println("Long (wrapper):  " + boxedMs + " ms");
                System.out.println("long (primitive): " + primitiveMs + " ms");
                // Long == long unboxes the Long, so this compares values
                System.out.println("Same answer: " + (boxedSum == primitiveSum));
            }
        }
    }
}

Output on our test machine:

Long (wrapper):  92 ms
long (primitive): 3 ms
Same answer: true

Both loops give the same answer, but the wrapper version was about 30 times slower for us. The line boxedSum += i looks harmless, yet on every pass it unboxes boxedSum, adds i, and boxes the result into a new Long object. Your exact times will differ, but the gap will be easy to see.

Keeping these performance issues in mind, you should:

  • Be careful when using autoboxing with large datasets.
  • Avoid autoboxing in loops and code that runs very often.
  • Limit autoboxing and unboxing in systems where memory is tight.
  • Use primitive types, primitive arrays like int[], and primitive streams like IntStream where speed matters, instead of relying on autoboxing.

Common Pitfalls And Best Practices For Autoboxing and Unboxing

Now, let us go through the most common autoboxing mistakes and the best practices that help you avoid them. These are the problems that show up again and again in student Java assignments.

Common pitfalls and best practices for Java autoboxing and unboxing

1. Avoid unnecessary boxing and unboxing

As you saw above, too much autoboxing and unboxing can lead to memory and performance problems. Avoid it in code that runs very often, like tight loops.

2. Watch out for null when unboxing

A wrapper class object can hold null, but a primitive cannot. If Java tries to unbox null, it throws a NullPointerException. This often happens with a Map, because get() returns null when the key is missing:

import java.util.HashMap;
import java.util.Map;

public class UnboxingNull {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Ana", 91);

        int benScore = scores.get("Ben");   // there is no "Ben", so get() returns null
        System.out.println(benScore);
    }
}

Output:

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
    at UnboxingNull.main(UnboxingNull.java:9)

The error message gives the problem away: Integer.intValue() is the hidden unboxing call. The program never called that method directly, but unboxing did. (Java 15 and later show this detailed message. Older versions only print java.lang.NullPointerException.) To fix it, check for null first, or give Java a default value to use when the key is missing:

import java.util.HashMap;
import java.util.Map;

public class UnboxingNullFix {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Ana", 91);

        int benScore = scores.getOrDefault("Ben", 0);  // 0 when "Ben" is missing
        System.out.println(benScore);
    }
}

Output:

0

Our guide to common Java errors covers other causes of NullPointerException and how to fix them.

3. Compare wrapper objects with equals(), not ==

This one surprises almost every student. Look at what happens when we compare Integer objects:

public class IntegerCompare {
    public static void main(String[] args) {
        Integer a = 127, b = 127;
        Integer c = 128, d = 128;

        System.out.println(a == b);        // true: small values are cached
        System.out.println(c == d);        // false: two different objects
        System.out.println(c.equals(d));   // true: compares the values
    }
}

Output:

true
false
true

With wrapper objects, == checks whether two variables point to the same object, not whether they hold the same value. By default, Java caches Integer objects for values from -128 to 127, so boxing 127 twice gives you the same object, and == happens to return true. The value 128 is outside the cache, so Java creates two separate objects, and == returns false. Always use equals() to compare wrapper values, or unbox them to int first. (If one side of == is a primitive, Java unboxes the other side and compares values. That is why the check at the end of the speed test above works.)

4. Understand the cost of wrapper classes in collections

If you are working on a system that needs fast execution and high performance, consider using primitive arrays instead of collections of wrapper objects. Also, avoid overloaded methods that differ only by int vs Integer, because it makes it harder to tell which method will be called.

Conclusion

In summary, primitive data types store the actual value and are more memory efficient. In contrast, reference data types store a reference, which means they point to the address of the actual data.

Wrapper classes connect the two. Autoboxing and unboxing are convenient and make your code easier to read, but they can also slow down your program and cause surprises like NullPointerException. So be careful not to overuse them.

Understanding which data type to use in software applications is important, because it helps you avoid errors and save memory. If you are still confused about Java topics like these, you can always reach out to our expert Java tutors for 1:1 live sessions.

Takeaways:

  • Java data types tell the compiler what kind of value a variable holds and how much memory it needs. Whether you are using numbers or characters, there is a data type for all!
  • There are eight primitive data types in Java. Six of them are meant for numbers: four for whole numbers (byte, short, int, long) and two for decimals (float, double). A char is stored as a number too, but you use it for characters.
  • Non-primitive data types, like arrays, String, classes, and interfaces, store references and can be null.
  • Every primitive has a wrapper class. Autoboxing and unboxing convert between them for you, but compare wrappers with equals(), watch out for null, and prefer primitives in performance-critical code.

Frequently Asked Questions

1. How many data types are there in Java?

Java has 8 primitive data types: byte, short, int, long, float, double, char, and boolean. Everything else, like String, arrays, classes, and interfaces, is a non-primitive (reference) type. There is no fixed number of reference types, because you create a new one every time you write a class.

2. What is the difference between primitive and non-primitive data types in Java?

A primitive stores the actual value, has a fixed size, and can never be null. A non-primitive stores a reference to an object, can be null, and can have methods. For example, int is primitive, while String and Integer are non-primitive.

3. Is String a primitive data type in Java?

No. String is a class, so it is a non-primitive (reference) type. It feels like a primitive because Java lets you create one with double quotes, like "Hello", and join strings with +. But a String variable holds a reference to an object, it has methods like length(), and it can be null.

4. What is autoboxing and unboxing in Java?

Autoboxing is Java automatically turning a primitive into its wrapper object, like int into Integer. Unboxing is the reverse, like Integer into int. Java has done this for you since Java 5. It happens when you assign values, pass them to methods, or store them in collections like ArrayList<Integer>.

5. Why does == return false when comparing two Integer objects?

Because == compares object references, not values. Java caches Integer objects from -128 to 127 by default, so two boxed values in that range share one object, and == happens to return true. Outside that range, like 128, you get two separate objects, and == returns false. Use equals() to compare wrapper values.

6. Why do I get a NullPointerException when unboxing?

A wrapper variable like Integer can be null, but an int cannot. When Java tries to unbox a null value into an int, it throws a NullPointerException. This often happens with Map.get() when the key is missing. Check for null first, or use getOrDefault() to supply a default value.

7. What is the default value of int in Java?

The default value of an int field or int array element is 0. Local variables inside a method get no default value. If you use one before giving it a value, the compiler stops with an error like "variable count might not have been initialized."

8. Should I use float or double in Java?

Use double for most decimal math. It is the default decimal type in Java and is more precise, with about 15 to 16 significant digits compared to 6 to 7 for float. Use float only when memory matters, like in very large arrays. For money, use neither: use BigDecimal, or store amounts in cents as a long.