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.
Sir, I wrote kabirList = list. Shouldn't Kabir get his own copy?
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.
So we both have pins to the same house, and Kabir put samosas inside it!
Exactly. That's a reference. Let's see where the house and the pins live in memory.
What is a reference type?
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.
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
new, stored on the heap.NullPointerException.==, which compares references.Primitive vs reference at a glance
| Primitive | Reference | |
|---|---|---|
| Examples | int, double, char, boolean… | String, arrays, ArrayList, Scanner, your classes |
| Variable holds | The value itself | An arrow to an object (or null) |
| Lives | In the stack frame (for locals) | Variable on the stack, object on the heap |
b = a copies | The value | The arrow: both share one object |
== compares | Values | Whether it's the same object |
| Default (fields) | 0, 0.0, false | null |
| Has methods? | No | Yes: 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.
Kinds of reference types
Classes
Ready-made ones like String, StringBuilder, Scanner, LocalDate, and your own like Student.
Arrays
int[], String[]… Even an array of primitives is an object on the heap (Module 4).
Interfaces
Types such as List or Runnable. A List variable can refer to an ArrayList object (Module 7).
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.
Ways to get a reference
| How | Example | What happens |
|---|---|---|
new | new 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 result | LocalDate.now(), name.toUpperCase() | The method creates or finds an object and returns its reference |
| Copy another reference | kabirList = list | No new object; one more arrow to the same object |
null | String name = null; | No object at all |
From new to garbage: the life of an object
Declare a reference variable
A slot is created on the stack that can hold a reference. No object exists yet.StringBuilder list;Create an object with new
list = new StringBuilder("chai");newasks the JVM for memory on the heap, builds the object (running its constructor) and returns its reference, which is stored inlist.Use it through the reference
The dot means “follow the arrow, then call the method on that object”. The object on the heap changes; the reference inlist.append(", samosa");liststays the same.Share it
Only the reference is copied. Now two arrows point to one object. A change through either variable is visible through both.StringBuilder kabirList = list;Let it go
Once no reference points to the object, it's unreachable. The garbage collector frees its memory later. You never delete objects yourself.list = null; kabirList = null;
Values vs arrows: both views
Left: the code and what prints. Right: the stack and the heap.
- With primitives,
b = acopies the number. Changing b leaves a at 10. new StringBuilder("chai")creates an object;listrefers to it.kabirList = listlooks like a copy, but no new object is created.- Kabir appends “samosa”, and printing Riya's list shows it too.
- Two Strings with the same text:
==says false,equalssays true. - After
list = null, callinglist.length()throws a NullPointerException.
- Two primitive variables are two separate boxes on the stack. The heap isn't involved.
newputs a StringBuilder object (#1) on the heap.liston the stack holds an arrow to it.kabirList = listcopies the arrow. Two variables now alias one object.appendfollows the arrow and changes object #1. Both variables see the change.- Each
new Stringmakes a separate object.==compares the arrows;equalscompares the contents. listnow holds null. There's nothing to follow, so the JVM throwsNullPointerException.
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.
- Create:
new Customer("Kabir")makes object #1 on the heap. The variablecon the stack holds a reference (an arrow) to it. - Missing data: the address field is a reference too, but nobody set it, so it holds
null: an arrow that points to no object. - Crash:
c.address.cityfollowscto #1 (fine), then tries to followaddress, which leads nowhere. Java throwsNullPointerExceptionand names the null part. - Fix: check
c.address != nullbefore following it. - Result: the app shows “Add address” instead of crashing. Null checks at the edges (forms, APIs, database rows) prevent most NPEs in production.
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.
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.
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).
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.
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.
Examples are simplified to show where references matter. Real designs vary.
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));
}
}new StringBuilder("chai")Creates an object on the heap.listholds its reference.StringBuilder kabirList = list;Copies the reference: no new object.kabirList.append(…)Changes the shared object, solistsees the change.s1 == s2Are they the same object? No: twonewcalls, two objects.s1.equals(s2)Is the text the same? Yes.coupon != nullChecking for null before using a reference is a common safety habit.
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.
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.
cart "Shoes"; checkout adds ", Gift wrap"
cart: Shoes, Gift wrap checkout: Shoes, Gift wrap same object? true
Approach
- Create one StringBuilder and point
cartat it. checkout = cartcopies the reference, not the object.- Append through
checkout, print throughcart.
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.
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.
"Hi", name "Asha"
HI, ASHA original still: Hi
Approach
- Keep the original in
first. - Build
greetwith+andtoUpperCase(). - 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.
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.
4 rooms; Asha in room 1 (index 0), Ravi in room 3 (index 2)
Room 1: Asha Room 2: null Room 3: Ravi Room 2 empty? true
Approach
new String[4]: four references, all null.- Assign two names.
- 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.
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 = acopies 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.
String methods
For "Bengaluru", print its length, first character and upper-case form.
Bengaluru
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());
}
}How it works: reference types have methods; primitives don't.
Default value of a reference
Create a String[] of size 2 and print its first element.
(none)
null
Show solution and explanation
public class Main {
public static void main(String[] args) {
String[] names = new String[2];
System.out.println(names[0]);
}
}How it works: reference slots start as null; println prints the word null.
Array length
Create an int array {5, 10, 15, 20} and print its length and last element.
{5, 10, 15, 20}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]);
}
}How it works: the last index is always length − 1.
== vs equals
Compare two Strings "pune" built differently with == and equals.
(none)
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));
}
}How it works: == compares references (different objects); equals compares the text. Always use equals for Strings.
Shared array
Make b refer to the same array as a, change b[0] to 99, and print a[0].
a = {1, 2, 3}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]);
}
}How it works: arrays are objects; assigning copies the reference, so a and b are the same array.
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.
null, then "Meena"
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);
}
}How it works: checking for null before using a reference prevents NullPointerException.
Coupon code comparison
A user types "save50". The valid code is "SAVE50". Compare ignoring case.
save50
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"));
}
}How it works: equalsIgnoreCase compares text without caring about capitals.
Order list as an array
Store 3 order ids in a String array and print them with their positions.
ORD-11, ORD-12, ORD-13
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]);
}
}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.
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 ==.”
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).
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.
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.