Reference types and how they differ

Primitive variables hold their value directly. Everything else in Java (strings, arrays, lists, your own classes) is a reference type: the variable holds an arrow to an object somewhere on the heap. This one idea explains shared changes, == vs equals, and Java's most famous error, NullPointerException.

Module 2 · Language Basics⏱ 17 minBeginnerNo install needed8 practice problems
1 The story

The shared shopping list

Riya and Kabir are organising snacks for the class party. Riya makes a list and gives Kabir “her list” in the program. Kabir adds samosas. When Riya prints her list, the samosas are there too, even though she never touched it. She's confused.

list→ kabirList→ one object on the heap "chai, samosa" STACK: two variables HEAP: only one list myphatshala.in
Riya

Sir, I wrote kabirList = list. Shouldn't Kabir get his own copy?

Arjun Sir

For an int, yes: the number is copied. But a list is an object. Your variable doesn't hold the list; it holds the address of the list, like a WhatsApp location pin. When you copied the variable, you copied the pin, not the house.

Riya

So we both have pins to the same house, and Kabir put samosas inside it!

Arjun Sir

Exactly. That's a reference. Let's see where the house and the pins live in memory.

2 Definition

What is a reference type?

Formal definition (write this in exams)

A reference type is any type that is not one of the eight primitives: classes (such as String), interfaces, arrays, enums and records. A variable of a reference type stores a reference to an object allocated on the heap, or the special value null, meaning it refers to no object. Assigning one reference variable to another copies the reference, not the object, so both refer to the same object.

In simple words

Primitive box: the value is inside. Reference box: an arrow is inside, pointing to an object that lives elsewhere (the heap). Copying the box copies the arrow. null means the arrow points nowhere.

Words you'll meet in this lesson

ObjectA bundle of data and behaviour created with new, stored on the heap.
ReferenceA value that identifies an object, like an address or pin. You can't see or do arithmetic on it.
HeapThe JVM memory area where all objects live, shared by the whole program.
AliasingTwo or more variables referring to the same object.
nullA reference to nothing. Using it like an object causes NullPointerException.
equals()A method that compares the contents of objects, unlike ==, which compares references.

Primitive vs reference at a glance

PrimitiveReference
Examplesint, double, char, boolean…String, arrays, ArrayList, Scanner, your classes
Variable holdsThe value itselfAn arrow to an object (or null)
LivesIn the stack frame (for locals)Variable on the stack, object on the heap
b = a copiesThe valueThe arrow: both share one object
== comparesValuesWhether it's the same object
Default (fields)0, 0.0, falsenull
Has methods?NoYes: name.length()

Exam tip: Java is always pass-by-value, even for objects: what gets copied is the value of the reference (the arrow). That's why two variables can share an object. Also: use equals(), not ==, to compare the text of two Strings.

3 Types and ways to use it

Kinds of reference types

Classes

Ready-made ones like String, StringBuilder, Scanner, LocalDate, and your own like Student.

Most common

Arrays

int[], String[]… Even an array of primitives is an object on the heap (Module 4).

Objects too

Interfaces

Types such as List or Runnable. A List variable can refer to an ArrayList object (Module 7).

Contracts

Enums and records

Fixed sets of constants (enum Day) and compact data classes (record Point(int x, int y)), covered in Modules 10 and 14.

Special classes

Ways to get a reference

HowExampleWhat happens
newnew StringBuilder("chai")A brand-new object on the heap; you get its reference
String literal"chai"A String from the string pool; the same literal is shared
Method resultLocalDate.now(), name.toUpperCase()The method creates or finds an object and returns its reference
Copy another referencekabirList = listNo new object; one more arrow to the same object
nullString name = null;No object at all
4 Step by step

From new to garbage: the life of an object

  1. Declare a reference variable

    StringBuilder list;
    A slot is created on the stack that can hold a reference. No object exists yet.
  2. Create an object with new

    list = new StringBuilder("chai");
    new asks the JVM for memory on the heap, builds the object (running its constructor) and returns its reference, which is stored in list.
  3. Use it through the reference

    list.append(", samosa");
    The dot means “follow the arrow, then call the method on that object”. The object on the heap changes; the reference in list stays the same.
  4. Share it

    StringBuilder kabirList = list;
    Only the reference is copied. Now two arrows point to one object. A change through either variable is visible through both.
  5. Let it go

    list = null;
    kabirList = null;
    Once no reference points to the object, it's unreachable. The garbage collector frees its memory later. You never delete objects yourself.
5 See it happen

Values vs arrows: both views

Left: the code and what prints. Right: the stack and the heap.

Riya's shared list 6 steps
What you see int a = 10;int b = a;b = 20;System.out.println(a); StringBuilder list = new StringBuilder("chai"); StringBuilder kabirList = list; kabirList.append(", samosa");System.out.println(list); String s1 = new String("hi");String s2 = new String("hi");s1 == s2 s1.equals(s2) list = null;System.out.println(list.length()); 10 (nothing printed yet) chai, samosa false true Exception … NullPointerException: Cannot invoke "java.lang.StringBuilder.length()" because "list" is null Changing b didn't change a. One new object is created. Looks like a copy… is it? Riya's list changed too! Same text, but not the same object. Calling a method on null crashes. myphatshala.in
Behind the screen STACK HEAP a10 b20 (heap not used) list → null kabirList→ StringBuilder #1 "chai" "chai, samosa" s1→ s2→ String #2 "hi" String #3 "hi" StringBuilder #1"chai, samosa" #1 (not via list) ✗ no arrow Primitives: two boxes, two separate values. new: object on heap, arrow on stack. Only the arrow was copied: aliasing. One object, changed once, seen by both. == compares arrows (#2 vs #3): false. equals() compares the text inside: true. null has no object to call a method on. myphatshala.in
6 Real-world use

References in real applications

Almost every value in a business application is an object. Understanding references prevents some of the most common production bugs.

Watch it with an example: a customer with no address

A delivery app stores each customer as an object. One customer skipped the address field. Follow the references and see where the famous NullPointerException comes from.

Following a reference to nowhere 5 steps
Animated example Customer c = new Customer("Kabir"); // c.address was never set String city = c.address.city; if (c.address != null) city = c.address.city; else System.out.println("Add address"); STACK HEAP c → #1 #1 Customer name → "Kabir" address = null → ? NullPointerException: Cannot read field "city" because "c.address" is null check first: address is null → skip Console: Add address no crash; the app asks the user instead myphatshala.in

1. One customer object, many screens

An app loads a Customer object once. The profile screen, order screen and support screen all hold references to the same object, so an address update is seen everywhere without copying.

Load customer→many references, one object→Consistent screens
NPE

2. NullPointerException in production

A missing middle name, an absent coupon or an unlinked bank account often arrives as null. Calling a method on it is one of the most common causes of crashes in Java services, which is why teams check for null, use Optional (Module 14) and validate inputs.

API request without coupon→coupon == null?→Handle safely
"UPI" == "UPI" ?.equals() ✓

3. Comparing text from users and APIs

Strings arriving from a web form or a JSON request are new objects. Comparing them with == fails even when the text matches, so code always uses equals (or equalsIgnoreCase).

paymentMode from request→"UPI".equals(paymentMode)→Correct branch

4. Caches

Caches store references to frequently used objects (like product details) so they don't need to be loaded from the database again. If code modifies a cached object by accident, every user sees the wrong data, which is why cached objects are often made immutable.

Product request→cache returns same object→Fast response
GC frees

5. Memory leaks through forgotten references

The garbage collector frees only objects that nothing refers to. A list that keeps references to every old session “just in case” keeps them alive forever, and memory slowly fills up. That's a memory leak, even in Java.

static list keeps adding→objects stay reachable→OutOfMemoryError

Examples are simplified to show where references matter. Real designs vary.

7 Run the code

Shared objects, == and equals

public class Main {
    public static void main(String[] args) {
        StringBuilder list = new StringBuilder("chai");
        StringBuilder kabirList = list;          // same object
        kabirList.append(", samosa");
        System.out.println("Riya's list: " + list);

        String s1 = new String("hi");
        String s2 = new String("hi");
        System.out.println(s1 == s2);            // same object?
        System.out.println(s1.equals(s2));       // same text?

        String coupon = null;
        System.out.println("Coupon present: " + (coupon != null));
    }
}
Riya's list: chai, samosa false true Coupon present: false
  • new StringBuilder("chai")Creates an object on the heap. list holds its reference.
  • StringBuilder kabirList = list;Copies the reference: no new object.
  • kabirList.append(…)Changes the shared object, so list sees the change.
  • s1 == s2Are they the same object? No: two new calls, two objects.
  • s1.equals(s2)Is the text the same? Yes.
  • coupon != nullChecking for null before using a reference is a common safety habit.
8 Line by line

Follow the arrows

Watch the stack hold arrows while objects appear on the heap. Notice which lines create objects and which only copy arrows.

Worked examples: real problems, solved and traced in memory

A reference variable doesn't hold the object; it holds the way to reach it. These three programs show the consequences: two variables can share one object, Strings never change in place, and an empty reference is null. Watch the arrows from the stack to the heap.

Example 1E-commerce · cart

Two references, one shopping cart

Problem statement

A shopping app passes the same cart to the checkout screen. The checkout adds a gift wrap. Because both variables refer to the same object, the change is visible through both. Use a StringBuilder as a simple cart.

Input
cart "Shoes"; checkout adds ", Gift wrap"
Expected output
cart:     Shoes, Gift wrap
checkout: Shoes, Gift wrap
same object? true

Approach

  1. Create one StringBuilder and point cart at it.
  2. checkout = cart copies the reference, not the object.
  3. Append through checkout, print through cart.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 2Messaging · text

Strings never change: each change makes a new String

Problem statement

A chat app builds a greeting: "Hi", then adds a name, then makes it upper case. Show that each step creates a new String object while the old one stays unchanged.

Input
"Hi", name "Asha"
Expected output
HI, ASHA
original still: Hi

Approach

  1. Keep the original in first.
  2. Build greet with + and toUpperCase().
  3. Print the original to prove it didn't change.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 3Hotel · rooms

Empty rooms are null

Problem statement

A small hotel has 4 rooms; a String[] holds the guest name for each. New arrays of references start full of null. Check in two guests and print rooms 1–3, then check whether room 2 is empty.

Input
4 rooms; Asha in room 1 (index 0), Ravi in room 3 (index 2)
Expected output
Room 1: Asha
Room 2: null
Room 3: Ravi
Room 2 empty? true

Approach

  1. new String[4]: four references, all null.
  2. Assign two names.
  3. Print the slots, then test one with == null.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

9 Quiz and revision

Check yourself

Quick revision

Lesson 2.3 in one screen

  • Reference types: classes, interfaces, arrays, enums, records. Everything except the 8 primitives.
  • Variable holds an arrow; the object lives on the heap.
  • b = a copies the arrow → aliasing: changes through one are seen by the other.
  • == = same object? equals() = same contents?
  • null = no object. Calling a method on it → NullPointerException.
  • Unreachable objects are freed by the GC; you never delete objects yourself.
  • Java is always pass-by-value; for objects, the reference is the value.

Interview and viva questions

What is the difference between primitive and reference types?

Primitives hold their value directly and have no methods; there are exactly eight. Reference types hold a reference to an object on the heap (or null), objects can have fields and methods, and assignment copies the reference rather than the object.

Does Java have pointers?

Java has references, which are like safe pointers: you can store, copy and compare them, but you can't see the address, do arithmetic on it, or turn a number into a reference. The JVM manages the actual addresses and can move objects during garbage collection.

Why does "a" == "a" sometimes return true?

Identical string literals are stored once in the string pool, so both refer to the same object and == is true. Strings built at run time (new String, user input, concatenation of variables) are usually separate objects. Never rely on == for text; use equals().

How do you avoid NullPointerException?

Initialise variables, validate inputs, check x != null before use, call equals on the known non-null side ("UPI".equals(mode)), return empty collections instead of null, and use Optional for values that may be absent.

How do you make a real copy of an object?

Create a new object with the same contents: a copy constructor (new StringBuilder(list)), clone() for arrays, or copy methods such as List.copyOf. For objects containing other objects, decide between a shallow and a deep copy.

Practice problems

Work with references: Strings, arrays and objects, null, and the difference between == and equals. Every output is fixed. The last three are business tasks.

P1Easy

String methods

For "Bengaluru", print its length, first character and upper-case form.

Input
Bengaluru
Expected output
9 B BENGALURU
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        String city = "Bengaluru";
        System.out.println(city.length() + " " + city.charAt(0) + " " + city.toUpperCase());
    }
}
9 B BENGALURU

How it works: reference types have methods; primitives don't.

P2Easy

Default value of a reference

Create a String[] of size 2 and print its first element.

Input
(none)
Expected output
null
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        String[] names = new String[2];
        System.out.println(names[0]);
    }
}
null

How it works: reference slots start as null; println prints the word null.

P3Easy

Array length

Create an int array {5, 10, 15, 20} and print its length and last element.

Input
{5, 10, 15, 20}
Expected output
4 20
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        int[] a = {5, 10, 15, 20};
        System.out.println(a.length + " " + a[a.length - 1]);
    }
}
4 20

How it works: the last index is always length − 1.

P4Medium

== vs equals

Compare two Strings "pune" built differently with == and equals.

Input
(none)
Expected output
false true
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        String a = "pune";
        String b = new String("pune");
        System.out.println((a == b) + " " + a.equals(b));
    }
}
false true

How it works: == compares references (different objects); equals compares the text. Always use equals for Strings.

P5Medium

Shared array

Make b refer to the same array as a, change b[0] to 99, and print a[0].

Input
a = {1, 2, 3}
Expected output
99
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        int[] a = {1, 2, 3};
        int[] b = a;
        b[0] = 99;
        System.out.println(a[0]);
    }
}
99

How it works: arrays are objects; assigning copies the reference, so a and b are the same array.

P6Business problem

Customer name check

A form field may be null if the user skipped it. Print whether a name was given, before and after the user fills it in.

Input
null, then "Meena"
Expected output
Name given? false
Name given? true, name: Meena
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        String name = null;
        System.out.println("Name given? " + (name != null));
        name = "Meena";
        System.out.println("Name given? " + (name != null) + ", name: " + name);
    }
}
Name given? false Name given? true, name: Meena

How it works: checking for null before using a reference prevents NullPointerException.

P7Business problem

Coupon code comparison

A user types "save50". The valid code is "SAVE50". Compare ignoring case.

Input
save50
Expected output
Valid: true
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        String typed = "save50";
        System.out.println("Valid: " + typed.equalsIgnoreCase("SAVE50"));
    }
}
Valid: true

How it works: equalsIgnoreCase compares text without caring about capitals.

P8Business problem

Order list as an array

Store 3 order ids in a String array and print them with their positions.

Input
ORD-11, ORD-12, ORD-13
Expected output
1. ORD-11
2. ORD-12
3. ORD-13
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        String[] orders = {"ORD-11", "ORD-12", "ORD-13"};
        System.out.println("1. " + orders[0]);
        System.out.println("2. " + orders[1]);
        System.out.println("3. " + orders[2]);
    }
}
1. ORD-11 2. ORD-12 3. ORD-13

How it works: the array holds three references, each pointing to a String on the heap. (Loops will make this shorter in Module 3.)

Real-world scenario questions (asked in company interviews)

Reference bugs are subtle: the code compiles and often works in testing. Decide your answer first, then open the model answer.

Payments · production bugFresher

UPI payments routed to the wrong handler

A payment service checks if (request.getMode() == "UPI"). In unit tests with hard-coded strings, it works. In production, every UPI payment goes to the “unsupported mode” branch. Why?

The interviewer is checking: do you know the difference between == and equals for Strings, and why tests passed?

Show a strong answer

In tests, both sides were string literals, which share one pooled object, so == happened to be true. In production, the mode comes from parsed JSON: a new String object with the same text. == compares references, so it's false.

  • Fix: "UPI".equals(request.getMode()). Putting the literal first also avoids a NullPointerException if the mode is missing.
  • Better still, convert the input to an enum (PaymentMode.UPI) at the API boundary.
  • Static analysis tools flag == on Strings; enable them in the build.

Avoid saying: “Java compares strings by value with ==.”

Travel booking · APIFresher

NullPointerException for some bookings

A booking API crashes for about 2% of requests with NullPointerException: Cannot invoke "java.lang.String.trim()" because the return value of "Passenger.getMiddleName()" is null. What would you do?

The interviewer is checking: can you read a modern NPE message and fix the root cause sensibly?

Show a strong answer

The message says exactly what happened: getMiddleName() returned null (many passengers have no middle name) and the code called trim() on it.

  • Decide what “no middle name” should mean: usually an empty value. Handle it where the name is read, for example middleName == null ? "" : middleName.trim().
  • Look for the same pattern elsewhere: any optional field from the client can be null.
  • Validate at the API boundary and document which fields are optional.
  • Add a test with a passenger who has no middle name.

Avoid: wrapping the whole method in catch (NullPointerException e).

E-commerce · pricing1–3 years

A discount that leaked to every customer

Product objects are cached in memory. For a VIP customer, a developer wrote Product p = cache.get(id); p.setPrice(p.getPrice() * 0.9); before showing the price. Soon all customers see the discounted price, and it keeps dropping. What's wrong?

The interviewer is checking: do you understand aliasing with shared cached objects?

Show a strong answer

cache.get(id) returns a reference to the shared cached object, not a copy. Calling setPrice changes that object for everyone, and every VIP request applies another 10% on top.

  • Never modify shared objects for one request. Calculate the VIP price in a local variable or a separate price object.
  • Make cached objects immutable (no setters, or records), so this mistake can't compile.
  • If a modified version is needed, create a copy explicitly.

Avoid: “Clear the cache every few minutes.” That hides the bug instead of fixing it.

SaaS · operations1–3 years

Memory keeps growing until the server crashes

A service's memory use grows slowly all week until it crashes with OutOfMemoryError. A developer says, “Impossible, Java has a garbage collector.” You find a static List<Session> allSessions that adds every user session and never removes any. Explain.

The interviewer is checking: do you know that the GC only frees unreachable objects?

Show a strong answer

The garbage collector frees objects only when nothing refers to them. The static list lives as long as the application and holds a reference to every session, so none of them can ever be collected. That's a memory leak.

  • Remove sessions when they end, or don't keep them in a long-lived list at all.
  • Use a cache with size limits and expiry (for example Caffeine) if keeping them is really needed.
  • Confirm with a heap dump: tools like Eclipse MAT show which objects keep others alive.

Avoid saying: “Just increase the heap size.” That only delays the crash.