JavaNon-Primitive Data Types

Non-Primitive Data Types

Alongside the eight primitive types, Java has reference types (also called non-primitive types). This category includes String, arrays, classes, and interfaces — basically everything that is not one of the eight primitives. Reference types behave very differently from primitives, and understanding that difference is essential to writing correct Java.
Common Non-Primitive Types
  • String — a sequence of characters (technically a class, not a primitive)

  • Arrays — e.g. int[], String[]

  • Classes — any type you define with the class keyword

  • Interfaces — contracts implemented by classes

References, Not Values

A primitive variable stores its value directly. A reference-type variable does not store the object itself — it stores a reference (an address) that points to where the actual object lives on the heap. This is the single most important behavioral difference between the two categories.

A reference points to an object

Java
public class Student {
    String name;

    Student(String name) {
        this.name = name;
    }
}
When you write Student s = new Student("Aman");, the variable s does not hold a Student object directly. It holds a reference to a Student object that was created somewhere on the heap. Think of it as an address, not the house itself.
Assignment Copies the Reference, Not the Object

This is where reference types diverge sharply from primitives. When you assign one reference variable to another, Java copies the reference (the address) — not the object it points to. After the assignment, both variables point at the exact same object in memory.

Two variables, one object

Java
public class ReferenceDemo {
    public static void main(String[] args) {
        Student s1 = new Student("Aman");
        Student s2 = s1; // s2 now points to the SAME object as s1

        s2.name = "Neha"; // modifies the shared object

        System.out.println(s1.name); // also "Neha"!
        System.out.println(s2.name);
    }
}
Neha
Neha
Warning
Changing s2.name also changed what s1.name reports, even though we never touched s1 directly. Both variables reference the same Student object, so there is really only one name field being changed. This trips up a lot of newcomers coming from languages where assignment always copies.
Compare this with the primitive behavior from the previous page, where assigning int b = a; gives b its own independent copy of the value. Reference types simply do not work that way.

Aspect

Primitive Types

Reference Types

What the variable holds

The actual value

A reference (address) to an object

Assignment

Copies the value (independent copies)

Copies the reference (shared object)

Can be null?

No

Yes

Default value

0 / 0.0 / false / '\u0000'

null

Default Value: null
Just like primitives get default values for uninitialized instance and static fields, reference-type fields get a default value too — it is simply null, meaning "this variable does not point to any object at all."

Uninitialized reference field

Java
public class Library {
    String bookTitle; // reference field, defaults to null

    public static void main(String[] args) {
        Library lib = new Library();
        System.out.println(lib.bookTitle); // prints "null"
    }
}
null
Note
As with primitives, this automatic default applies only to instance and static fields. A local reference variable still must be assigned before it is read — the compiler enforces this the same way it does for local primitives.
The Classic Consequence: NullPointerException
Because a reference variable can be null, trying to use it as though it points to a real object — calling a method on it, accessing a field — throws a NullPointerException (often nicknamed "NPE"). This is one of the most common runtime errors in all of Java.

Triggering a NullPointerException

Java
public class NpeDemo {
    public static void main(String[] args) {
        String message = null;

        // Calling a method on a null reference blows up at runtime
        System.out.println(message.length());
    }
}
Warning
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "message" is null. Unlike primitives, which are guaranteed to hold a real value, reference variables always carry the risk of being null, and Java makes you deal with that possibility.
Tip
NullPointerException is common enough that Java has an entire toolkit built around avoiding it — null checks, the Optional class, and defensive coding conventions. For now, the key habit to build is: before calling a method on a reference variable, make sure you know it can't be null, or check it explicitly.
Arrays Are Reference Types Too
It is easy to forget that arrays — even arrays of primitives like int[] — are themselves reference types. The array variable holds a reference to the array object on the heap, so the same "shared reference" behavior applies.

Arrays share references just like objects

Java
public class ArrayReferenceDemo {
    public static void main(String[] args) {
        int[] scores1 = {90, 85, 70};
        int[] scores2 = scores1; // same array object

        scores2[0] = 100;

        System.out.println(scores1[0]); // 100, not 90!
    }
}
100