Skip to content
Ameen Abozaid
PublishedBEG-009-L03

Strings and reference types

  • Lesson 3 of 7
  • 12 min read
  • Beginner
On This Lesson 11 topics

Lesson Overview

You have been using String since your first println. In this lesson we look at what a String really is, and why it is not one of the eight primitive types from Lesson 2. The difference is one of the most important ideas in Java, and it explains a long list of behaviors you will meet later.

Here is the picture. A primitive box holds its value inside the box: the box labeled age really contains 30. A reference box holds not the value itself but a pointer, like a slip of paper with an address on it. The text lives in a separate place in memory, and the box points to it. String works this way.

Why this matters: nearly all data a backend handles arrives as text, such as names, emails, JSON, and URLs. The moment you understand that a String variable holds a pointer, three things make sense: why a variable can hold null ("no object here"), why a program can crash when you use such a variable, and why text must be compared with .equals(...).

This lesson builds on Lessons 1 and 2. It deliberately keeps the memory picture simple. A deeper treatment (how objects are laid out, why == behaves as it does on text) comes in later modules.

Learning Objectives

By the end of this lesson you will be able to:

  • Declare and initialize a String, and join strings with +.
  • Explain the difference between primitive and reference types at a conceptual level.
  • Say what null means, and what happens when you use a String variable that holds null.
  • Use .length(), .toUpperCase(), and .equals(...) on a String.
  • Explain that a String object cannot be changed after it is created.
  • Compare text with .equals(...) instead of ==.

Written Explanation

The String type

A String holds text: zero or more characters. A string literal is written in double quotes.

java
String name = "Ana";
String empty = "";      // a String with no characters

Its name starts with a capital letter, which is a clue: the eight primitive types (Lesson 2) are all lowercase, while String is a class, a kind of reference type. Classes get their own module later. For now you need three facts about them.

Primitive versus reference

Java divides all types into two kinds: primitive types and reference types (the Java Language Specification, chapter 4). The eight types of Lesson 2 are the primitives. Everything else in the language you will use, starting with String, is a reference type.

Primitive box (int age = 30;)Reference box (String name = "Ana";)
What is inside the boxThe value 30 itselfA pointer to an object
Where the real data livesIn the boxIn a separate object elsewhere in memory
Can the box hold "nothing"?No: it always has a valueYes: it can hold null
Calling .something() on itNot possiblePossible, using the object it points to

A diagram helps:

text
 int age                 String name
 +------+                +--------+         +---------+
 |  30  |                |   --->-+-------->| "Ana"   |
 +------+                +--------+         +---------+
 value is in the box     box holds a pointer; text is in the object

(This is a model for learning. How Java stores objects in detail is not needed here.)

Copying: values versus pointers

When you assign one variable to another, what is copied is what is in the box. For a primitive that is the value. For a reference it is the pointer, so both boxes then point at the same object. Because a String cannot be changed (see below), this sharing is harmless for text, but the rule matters for every other class you will meet.

java
String a = "Ana";
String b = a;      // b points to the same text object
a = "Omar";        // a now points elsewhere; b is unaffected

After these lines a is Omar and b is still Ana. Reassigning a moved its pointer; it did not touch the text that b points to.

null: no object here

A reference box may hold the special value null, meaning "this box points to no object". It is the reference world's version of an empty box. The Language Specification describes null as the only value of a special null type that can be assigned to any reference type.

java
String middleName = null;

null is not the same as "". The empty string "" is a real String object containing zero characters. null is the absence of any object. Printing a null reference shows the word null. Using it, for example asking for its length, fails at run time with a NullPointerException, because there is no object to ask. A primitive can never be null, so this failure cannot happen with an int.

Strings cannot be changed

A String object is immutable: once created, its characters never change. Methods such as toUpperCase() do not alter the original; they return a new string. If you want to keep the result, assign it to a variable.

java
String name = "Ana";
name.toUpperCase();          // makes "ANA" but the result is thrown away
name = name.toUpperCase();   // now the variable points to the new text

A few things a String can do

Because a String is an object, you can call methods on it with a dot. A method is a named action the object knows how to do (methods get their own module later). Three to start with:

  • name.length() returns the number of characters, as an int.
  • name.toUpperCase() returns an upper-case copy.
  • name.equals(other) returns a boolean: true if the two texts have exactly the same characters.

The + operator joins strings and, when one side is text, converts the other side to text as well, as you saw in BEG-008.

Comparing text: use .equals

To ask whether two strings contain the same text, write first.equals(second). Do not use first == second for this. With references, == asks whether the two boxes point at the same object, which is a different question from whether the texts are equal, and the answer can surprise you. The full explanation is in a later module (BEG-026); for now, remember the rule: text is compared with .equals. Note also that .equals is case sensitive: "Ana" and "ana" are different.

Examples

Example 1: working with a String (simple)

java
public class Greeting {
    public static void main(String[] args) {
        String name = "Ana";
        String empty = "";
        System.out.println(name.length());
        System.out.println(empty.length());
        System.out.println(name.toUpperCase());
        System.out.println(name);
        name = name + " Hassan";
        System.out.println(name);
        String other = "Ana Hassan";
        System.out.println(name.equals(other));
        System.out.println("Ana".equals("ana"));
    }
}

Output (JDK 21):

text
3
0
ANA
Ana
Ana Hassan
true
false

Notice line 4 of the output: calling name.toUpperCase() printed ANA but name itself was still Ana. Then name = name + " Hassan" built a new string and made the variable point to it. The last two lines show .equals and its case sensitivity.

Example 2: two pointers, and a null (practical)

java
public class Pointers {
    public static void main(String[] args) {
        String a = "Ana";
        String b = a;
        a = "Omar";
        System.out.println(a + " " + b);

        String middle = null;
        System.out.println(middle);
        System.out.println("Length: " + middle.length());
    }
}

Output:

text
Omar Ana
null
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "<local3>" is null
	at Pointers.main(Pointers.java:10)

The first line confirms that b kept its pointer. The second prints null without a problem, because printing only needs to show it. The third asks a method of a non-existent object and the program stops with NullPointerException. The text says <local3> instead of middle because this file was compiled with the default javac options, which do not keep local variable names in the class file; other tools or options may show the name instead. The line number tells you where to look: line 10.

Reading a stack trace like this is a skill you will build up. For now the key fact: a null reference is fine to hold and print, but using it as an object crashes.

Example 3: text is not a number (realistic)

Data from forms, files, and requests usually arrives as text, even when it looks like a number. A String containing 42 is not the number 42:

java
public class TextVsNumber {
    public static void main(String[] args) {
        String text = "42";
        int number = 42;
        System.out.println(text + 1);
        System.out.println(number + 1);
    }
}

Output:

text
421
43

With text, + joins, so "42" plus 1 becomes 421. With a number, + adds. A backend that reads an age or quantity from a request has to convert the text to a number before doing arithmetic (a later module shows how). The compiler also protects you from mixing them silently:

text
error: incompatible types: int cannot be converted to String        (String s = 42;)
error: incompatible types: String cannot be converted to int        (int n = "42";)
error: int cannot be dereferenced                                   (int len = 5; len.length();)

The last message says "dereferenced" because following a pointer is exactly what the dot does, and an int has no pointer to follow.

Backend Insight

Strings are the everyday material of backend work: user names, email addresses, URLs, SQL text, JSON bodies, log lines. Three consequences of this lesson recur constantly:

  • Null handling. A value that is missing in a request or a database row often shows up as null. Code that uses such a value without checking can throw NullPointerException, one of the most common production errors in Java. Later modules show safe ways to handle missing values. The first step is to know where null can come from: only reference types.
  • Equality. Comparing a typed password, a status code like "PAID", or a user name must use .equals. == can seem to work in a quick test and fail with real data.
  • Immutability. Because strings do not change, they can be shared safely, for example as keys and in many parts of a program at once. Building a very long text by joining thousands of pieces creates many temporary strings; later modules introduce better tools for that.

Also keep in mind that text has an encoding when it travels over a network or into a file; Module 1 introduced the idea.

Common Mistakes

Mistake 1: Comparing text with ==

Beginners write if (name == "Ana") because it reads naturally and it may even seem to work in tiny tests. It tests whether two boxes point at the same object, not whether the text is equal. Use name.equals("Ana"). (Why they differ is explained in BEG-026.)

Mistake 2: Forgetting that the result of toUpperCase() must be kept

name.toUpperCase(); on its own changes nothing, because strings are immutable. Write name = name.toUpperCase(); or String shout = name.toUpperCase();.

Mistake 3: Calling a method on a null String

String middle = null; middle.length(); compiles but crashes with NullPointerException. Make sure a reference points to an object before you use it, or keep the "no value" case in mind from the start.

Mistake 4: Confusing null and the empty string

"" is an object with zero characters and .length() is 0. null is no object, and .length() crashes. They are different states of the box, and they mean different things.

Mistake 5: Using single quotes for text

String s = 'Ana'; is rejected, because single quotes mean a one-character char, and Ana is three characters. Text needs double quotes.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. Which of these are primitive types and which are reference types: double, String, boolean?

Show solution A1

A1. double and boolean are primitive; String is a reference type.

A2. What does a String variable hold in its box: the text itself, or something else?

Show solution A2

A2. Conceptually it holds a pointer (a reference) to a String object that contains the text, not the text itself.

A3. What is the length of "", and what does String s = null; mean?

Show solution A3

A3. The length of "" is 0: it is a real string with no characters. String s = null; means the variable points to no object at all.

Level B: Application

B1. Predict the output:

java
String x = "go";
String y = x;
x = x + "!";
System.out.println(x + " " + y);
Show solution B1

B1. x is first go, then go! (a new string). y still points at the original go. Output: go! go.

B2. Fix the bug: the programmer wants title to be in capitals, and prints it.

java
String title = "report";
title.toUpperCase();
System.out.println(title);
Show solution B2

B2. toUpperCase() returns a new string and leaves title unchanged, so it prints report. Fix: title = title.toUpperCase();, which makes it print REPORT.

B3. Write a short program that declares String user = "admin"; and prints whether it equals "Admin" and whether it equals "admin".

Show solution B3

B3.

java
String user = "admin";
System.out.println(user.equals("Admin"));
System.out.println(user.equals("admin"));

It prints false then true, because .equals is case sensitive.

Level C: Challenge

C1. Explain in four or five sentences how a String variable and an int variable differ in what they store, and give one thing that is possible with one but not the other.

Show solution C1

C1. An int variable stores the number itself in its box. A String variable stores a pointer to a text object that lives elsewhere. A String can be null and can have methods called on it (name.length()), but an int cannot be null and has no methods, so age.length() does not compile. The pointer also means that two String variables can refer to the same text.

C2. A method returns a String that might be null. Describe what goes wrong in result.length() if it is, and what question you should ask before writing that call.

Show solution C2

C2. If the value is null, there is no object to ask, so the call throws NullPointerException and the program stops. Before writing the call, ask "can this value be missing?" and decide what the program should do in that case.

Mini Challenge

Write a program Badge with three String variables: firstName, lastName, and role (for example "guest"). Build a fourth string fullName by joining the first two with a space. Print a line that says the full name in capitals, followed by the number of characters in fullName, followed by whether role equals "admin".

Check your work: you used + and .toUpperCase() correctly (and kept or printed the result), used .length() on the full name, compared with .equals and not ==, and the program compiles and prints a sensible line.

Summary & Next Lesson

Key takeaways

  • String holds text and is a reference type: its box holds a pointer to an object, not the text itself.
  • Primitives hold the value directly and can never be null. A reference can be null, which means "no object".
  • Using a null reference as an object throws NullPointerException.
  • String objects are immutable. Methods like toUpperCase() return a new string.
  • Compare text with .equals(...), not ==.

Next lesson

You know what boxes there are. The next lesson, "Literals, constants, and naming", looks at how to write values in code (literals), how to make a box that cannot be changed with final, and the Java rules for naming things.

Self-check

  1. Is String a primitive type?
  2. What is the difference between "" and null?
  3. What does name.toUpperCase(); do to name if nothing is assigned?
  4. Which should you use to compare text: == or .equals?
  5. Why can an int variable never throw a NullPointerException?
Show answers
  1. No, it is a reference type (a class).
  2. "" is a real string with no characters; null means no object at all.
  3. Nothing: strings are immutable, and the result is discarded.
  4. .equals.
  5. An int holds its value directly and can never be null, so there is no missing object to use.

References

  • Oracle, The Java Language Specification, Java SE 21, section 4.1. It states that the language has primitive and reference types, and that the null type's only value is null, which can be assigned to any reference type.
  • Oracle, The Java Tutorials: Primitive Data Types. It says String is not a primitive but has special language support, and that String objects are immutable.
  • The outputs and error messages above were produced by compiling and running the code with JDK 21. The <local3> wording in the NullPointerException message is observed behavior of the default compile options, not something documented here; other JDK versions or options may differ. The pointer picture is a teaching model. I did not verify the claim about string joining creating many temporary strings against a source; it is stated from general knowledge and a later module covers it properly.

Practice

Apply this lesson with the module's lab, exercises and quiz.

Lab and exercises Quiz

Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.

Continue to next lesson

Type to search levels, phases, modules and lessons.