Java Tutorial
🔍

Java String Basics

Java String Basics

Every Java program deals with text — usernames, messages, product names, addresses, error messages. All of that text is handled using String. It is the most used class in Java, and also one of the most misunderstood.

A String in Java is a sequence of characters. What makes it different from a plain char[] is that String comes with built-in methods for searching, comparing, transforming, and combining text — and it has a special behaviour called immutability that every developer must understand to avoid bugs and write efficient code.

Creating Strings — Two Ways

There are two ways to create a String in Java, and they behave differently in memory.

// File: StringCreationDemo.java public class StringCreationDemo { public static void main(String[] args) { // Way 1 — String literal (most common, recommended) // Java stores this in the String Pool — a special memory area String name1 = "Priya"; String name2 = "Priya"; // reuses the same object from the pool // Way 2 — new keyword (creates a new object every time) String name3 = new String("Priya"); String name4 = new String("Priya"); // another new object // == compares references (memory addresses) System.out.println("name1 == name2 : " + (name1 == name2)); // true — same pool object System.out.println("name1 == name3 : " + (name1 == name3)); // false — different objects System.out.println("name3 == name4 : " + (name3 == name4)); // false — both new objects // .equals() compares content — always use this for comparison System.out.println("name1.equals(name3) : " + name1.equals(name3)); // true — same content System.out.println("name3.equals(name4) : " + name3.equals(name4)); // true — same content } }
Output:
name1 == name2 : true
name1 == name3 : false
name3 == name4 : false
name1.equals(name3) : true
name3.equals(name4) : true
Memory picture:

String Pool (special area in heap):
  ┌──────────────┐
  │  "Priya"     │ ◄── name1 and name2 both point here
  └──────────────┘

Heap (regular area):
  ┌──────────────┐   ┌──────────────┐
  │  "Priya"     │   │  "Priya"     │
  └──────────────┘   └──────────────┘
       ▲                    ▲
     name3                name4

Use String literals (String s = "hello") in almost all situations. They are more memory-efficient because Java reuses the same object for identical strings in the pool. Use new String(...) only in rare cases where you explicitly need a separate object.

String Immutability — The Most Important Concept

A String object cannot be changed after it is created. This is called immutability. When you call methods like toUpperCase(), replace(), or trim() on a String, they do not modify the original — they return a brand new String object with the result.

// File: ImmutabilityDemo.java public class ImmutabilityDemo { public static void main(String[] args) { String city = "mumbai"; System.out.println("Original : " + city); // toUpperCase() does NOT change 'city' — it returns a NEW String String upperCity = city.toUpperCase(); System.out.println("city after toUpperCase() : " + city); // still "mumbai" System.out.println("upperCity : " + upperCity); // "MUMBAI" // Common beginner mistake — forgetting to capture the result city.toUpperCase(); // result is thrown away — city is unchanged System.out.println("city after ignored call : " + city); // still "mumbai" // Correct — capture the returned string city = city.toUpperCase(); // reassign to update the variable System.out.println("city after reassignment : " + city); // "MUMBAI" System.out.println(); // Chaining is safe because each step returns a new String String messy = " Hello, World! "; String cleaned = messy.trim().toLowerCase().replace(",", ""); System.out.println("messy : '" + messy + "'"); System.out.println("cleaned: '" + cleaned + "'"); // messy is completely unchanged } }
Output:
Original : mumbai
city after toUpperCase() : mumbai
upperCity                : MUMBAI
city after ignored call  : mumbai
city after reassignment  : MUMBAI

messy  : '  Hello, World!  '
cleaned: 'hello world!'

The most common beginner mistake is calling a String method and ignoring the return value — and then wondering why the String did not change. The rule is: always capture the result. String methods never modify the object they are called on.

String Length and Character Access

// File: StringLengthCharDemo.java public class StringLengthCharDemo { public static void main(String[] args) { String message = "Hello, India!"; // length() — number of characters including spaces and punctuation System.out.println("String : " + message); System.out.println("Length : " + message.length()); // 13 // charAt(index) — character at a specific position (0-based index) System.out.println("charAt(0) : " + message.charAt(0)); // H System.out.println("charAt(7) : " + message.charAt(7)); // I System.out.println("Last char : " + message.charAt(message.length() - 1)); // ! System.out.println(); // Loop through every character System.out.println("Characters one by one:"); for (int i = 0; i < message.length(); i++) { System.out.print(message.charAt(i) + " "); } System.out.println(); System.out.println(); // isEmpty() — true only if length is 0 String empty = ""; String spaces = " "; String text = "hello"; System.out.println("empty.isEmpty() : " + empty.isEmpty()); // true System.out.println("spaces.isEmpty() : " + spaces.isEmpty()); // false — has spaces System.out.println("text.isEmpty() : " + text.isEmpty()); // false // isBlank() — true if empty OR only whitespace (Java 11+) System.out.println("empty.isBlank() : " + empty.isBlank()); // true System.out.println("spaces.isBlank() : " + spaces.isBlank()); // true — only spaces System.out.println("text.isBlank() : " + text.isBlank()); // false } }
Output:
String  : Hello, India!
Length  : 13
charAt(0) : H
charAt(7) : I
Last char : !

Characters one by one:
H e l l o ,   I n d i a ! 

empty.isEmpty()  : true
spaces.isEmpty() : false
text.isEmpty()   : false
empty.isBlank()  : true
spaces.isBlank() : true
text.isBlank()   : false

isEmpty() only returns true for a string with zero characters. isBlank() (Java 11+) also returns true for strings that contain only spaces, tabs, or other whitespace — which is more useful when validating user input where someone might enter just spaces.

Common String Methods

These are the methods every Java developer uses daily.

// File: CommonStringMethods.java public class CommonStringMethods { public static void main(String[] args) { String sentence = " Welcome to DevStackFlow "; // trim() — removes leading and trailing whitespace System.out.println("Original : '" + sentence + "'"); System.out.println("trimmed : '" + sentence.trim() + "'"); // strip() — same as trim() but handles Unicode whitespace (Java 11+) System.out.println("stripped : '" + sentence.strip() + "'"); System.out.println(); String product = "Wireless Headphones - Premium Edition"; // toUpperCase() / toLowerCase() System.out.println("upper: " + product.toUpperCase()); System.out.println("lower: " + product.toLowerCase()); System.out.println(); // contains() — checks if a substring exists anywhere System.out.println("contains 'Headphones': " + product.contains("Headphones")); // true System.out.println("contains 'laptop' : " + product.contains("laptop")); // false // startsWith() / endsWith() System.out.println("starts 'Wireless': " + product.startsWith("Wireless")); // true System.out.println("ends 'Edition' : " + product.endsWith("Edition")); // true System.out.println(); // replace() — replaces all occurrences String original = "I love Java. Java is great. Java forever."; String replaced = original.replace("Java", "Python"); System.out.println("original : " + original); System.out.println("replaced : " + replaced); System.out.println(); // substring() — extracts a part of the string String fullName = "Priya Sharma"; System.out.println("Full name : " + fullName); System.out.println("First name : " + fullName.substring(0, 5)); // "Priya" System.out.println("Last name : " + fullName.substring(6)); // "Sharma" System.out.println(); // indexOf() — position of first occurrence (-1 if not found) String email = "priya@flipkart.com"; int atPos = email.indexOf('@'); System.out.println("Email : " + email); System.out.println("@ at pos : " + atPos); // 5 System.out.println("Username : " + email.substring(0, atPos)); // priya System.out.println("Domain : " + email.substring(atPos + 1)); // flipkart.com } }
Output:
Original : '  Welcome to DevStackFlow  '
trimmed  : 'Welcome to DevStackFlow'
stripped : 'Welcome to DevStackFlow'

upper: WIRELESS HEADPHONES - PREMIUM EDITION
lower: wireless headphones - premium edition

contains 'Headphones': true
contains 'laptop'    : false
starts 'Wireless': true
ends 'Edition'   : true

original : I love Java. Java is great. Java forever.
replaced : I love Python. Python is great. Python forever.

Full name  : Priya Sharma
First name : Priya
Last name  : Sharma

@ at pos : 5
Username : priya
Domain   : flipkart.com

String Comparison — equals() vs equalsIgnoreCase() vs ==

// File: StringComparisonDemo.java public class StringComparisonDemo { public static void main(String[] args) { String input = "Admin"; String stored = "admin"; String ref = input; // == compares references — almost always wrong for content comparison System.out.println("== check:"); System.out.println(" input == stored : " + (input == stored)); // false System.out.println(" input == ref : " + (input == ref)); // true (same object) System.out.println(); // equals() — exact content match, case-sensitive System.out.println("equals():"); System.out.println(" 'Admin'.equals('admin') : " + input.equals(stored)); // false System.out.println(" 'Admin'.equals('Admin') : " + input.equals("Admin")); // true // equalsIgnoreCase() — content match, case-insensitive System.out.println(); System.out.println("equalsIgnoreCase():"); System.out.println(" 'Admin'.equalsIgnoreCase('admin') : " + input.equalsIgnoreCase(stored)); // true // compareTo() — lexicographic comparison (like dictionary order) // Returns 0 if equal, negative if first < second, positive if first > second System.out.println(); System.out.println("compareTo():"); String a = "Apple"; String b = "Banana"; String c = "Apple"; System.out.println(" 'Apple' compareTo 'Banana' : " + a.compareTo(b)); // negative System.out.println(" 'Apple' compareTo 'Apple' : " + a.compareTo(c)); // 0 System.out.println(" 'Banana' compareTo 'Apple' : " + b.compareTo(a)); // positive System.out.println(); // Best practice — put the known string first to avoid NullPointerException String userInput = null; // Risky — throws NullPointerException if userInput is null // if (userInput.equals("admin")) { ... } // Safe — known non-null string calls equals boolean isAdmin = "admin".equals(userInput); System.out.println("Safe null check: " + isAdmin); // false — no NPE } }
Output:
== check:
  input == stored : false
  input == ref    : true

equals():
  'Admin'.equals('admin') : false
  'Admin'.equals('Admin') : true

equalsIgnoreCase():
  'Admin'.equalsIgnoreCase('admin') : true

compareTo():
  'Apple' compareTo 'Banana'  : -1
  'Apple' compareTo 'Apple'   : 0
  'Banana' compareTo 'Apple'  : 1

Safe null check: false

Always use equals() or equalsIgnoreCase() for string content comparison — never ==. When comparing against a known string constant, put the constant on the left side: "admin".equals(userInput). This way, even if userInput is null, no NullPointerException is thrown.

String Concatenation

// File: ConcatenationDemo.java public class ConcatenationDemo { public static void main(String[] args) { // + operator — simplest, works anywhere String firstName = "Rohan"; String lastName = "Mehta"; String fullName = firstName + " " + lastName; System.out.println("Full name : " + fullName); // concat() method — same as +, but only works with Strings String greeting = "Hello, ".concat(firstName).concat("!"); System.out.println("Greeting : " + greeting); // String.format() — structured output with placeholders String orderId = "ORD-501"; double amount = 1299.50; String status = "PLACED"; String summary = String.format("Order %s | Rs.%.2f | %s", orderId, amount, status); System.out.println("Summary : " + summary); System.out.println(); // Concatenation with non-String types — Java calls toString() automatically int age = 24; double salary = 45000.0; boolean active = true; System.out.println("Age: " + age); System.out.println("Salary: " + salary); System.out.println("Active: " + active); System.out.println(); // + with numbers — beware of operator precedence int x = 10, y = 20; System.out.println("Result: " + x + y); // "Result: 1020" — string concat System.out.println("Result: " + (x + y)); // "Result: 30" — arithmetic first System.out.println(x + y + " is the sum"); // "30 is the sum" — left to right System.out.println(); // StringBuilder — efficient for building strings in loops StringBuilder sb = new StringBuilder(); String[] items = {"Laptop", "Mouse", "Keyboard", "Monitor"}; for (int i = 0; i < items.length; i++) { sb.append(items[i]); if (i < items.length - 1) sb.append(", "); } System.out.println("Cart items : " + sb.toString()); } }
Output:
Full name   : Rohan Mehta
Greeting    : Hello, Rohan!
Summary     : Order ORD-501 | Rs.1299.50 | PLACED

Age: 24
Salary: 45000.0
Active: true

Result: 1020
Result: 30
30 is the sum

Cart items  : Laptop, Mouse, Keyboard, Monitor

The + operator creates a new String object every time. Inside a loop that concatenates many strings, this creates many temporary objects. StringBuilder avoids this — it accumulates all parts in a mutable buffer and converts to a String only once at the end with toString().

String Splitting and Joining

// File: SplitJoinDemo.java import java.util.Arrays; public class SplitJoinDemo { public static void main(String[] args) { // split() — breaks a string at a delimiter, returns String array String csvRow = "Priya,28,Bengaluru,Engineering"; String[] parts = csvRow.split(","); System.out.println("CSV row : " + csvRow); System.out.println("Parts : " + Arrays.toString(parts)); System.out.println("Name : " + parts[0]); System.out.println("Age : " + parts[1]); System.out.println("City : " + parts[2]); System.out.println("Dept : " + parts[3]); System.out.println(); // split with limit — limits number of resulting parts String sentence = "one:two:three:four:five"; String[] limited = sentence.split(":", 3); // max 3 parts System.out.println("Limited split: " + Arrays.toString(limited)); System.out.println(); // String.join() — joins elements with a separator String joined = String.join(", ", "Mumbai", "Delhi", "Bengaluru", "Pune"); System.out.println("Joined cities: " + joined); // join from an array String[] skills = {"Java", "SQL", "Spring Boot", "Git"}; String skillSet = String.join(" | ", skills); System.out.println("Skills : " + skillSet); System.out.println(); // Practical use — parse a full name String fullName = "Ananya Krishnan Iyer"; String[] nameParts = fullName.split(" "); System.out.println("First : " + nameParts[0]); System.out.println("Middle: " + nameParts[1]); System.out.println("Last : " + nameParts[2]); } }
Output:
CSV row : Priya,28,Bengaluru,Engineering
Parts   : [Priya, 28, Bengaluru, Engineering]
Name    : Priya
Age     : 28
City    : Bengaluru
Dept    : Engineering

Limited split: [one, two, three:four:five]

Joined cities: Mumbai, Delhi, Bengaluru, Pune
Skills       : Java | SQL | Spring Boot | Git

First : Ananya
Middle: Krishnan
Last  : Iyer

split() accepts a regular expression as the delimiter — not just a plain character. Splitting on . (dot) does not work as expected because . means "any character" in regex. To split on a literal dot, use split("\\.").

String vs StringBuilder vs StringBuffer — Comparison Table

AspectStringStringBuilderStringBuffer
MutabilityImmutable — cannot changeMutable — can modifyMutable — can modify
Thread safetyYes — immutable is always thread-safeNo — not thread-safeYes — all methods synchronized
PerformanceSlow in loops — creates new object each timeFast — modifies in placeSlower than StringBuilder — sync overhead
When to useSingle values, constants, comparisonsBuilding strings in loops or one threadBuilding strings shared across multiple threads
Main methodsHundreds of read methodsappend(), insert(), delete(), reverse()Same as StringBuilder
Can use +YesNo — use append()No — use append()
StorageString Pool or HeapHeap onlyHeap only
Java versionSince Java 1.0Since Java 1.5Since Java 1.0

Basic Example

public class Main { public static void main(String[] args) { String firstName = "Priya"; String lastName = "Sharma"; // Concatenation String fullName = firstName + " " + lastName; System.out.println("Full name : " + fullName); // Common methods System.out.println("Length : " + fullName.length()); System.out.println("Uppercase : " + fullName.toUpperCase()); // Comparison System.out.println("Is Priya? : " + firstName.equals("Priya")); } }

Output

Full name : Priya Sharma
Length    : 12
Uppercase : PRIYA SHARMA
Is Priya? : true

How it works:

  1. ›firstName + " " + lastName joins three strings into one new String, fullName.
  2. ›length() counts every character in fullName, including the space.
  3. ›toUpperCase() returns a new String with every letter capitalised — fullName itself is unchanged.
  4. ›equals() compares the actual text content of two strings, returning true because firstName holds exactly "Priya".

Best Practices

Always use .equals() to compare strings, never ==. The == operator checks whether two variables point to the same object in memory. Two strings with identical content stored as different objects will return false with ==. This is a bug that is easy to write and hard to spot. Use "constant".equals(variable) with the constant on the left to also avoid NullPointerException if the variable is null.

Always capture the result of a String method. String methods return new strings — they do not modify the original. name.trim() does nothing useful unless you write name = name.trim() or String trimmedName = name.trim(). Forgetting this is the single most common String mistake.

Use StringBuilder inside loops. Every + inside a loop creates a new String object. In a loop that runs 1,000 times, this creates 1,000 temporary String objects. StringBuilder.append() modifies the same buffer the entire time. For loops, StringBuilder is always the right choice.

Use String.format() for structured output. "Rs." + amount + " for " + qty + " items" becomes hard to read as the pieces multiply. String.format("Rs.%.2f for %d items", amount, qty) is readable, predictable, and formats numbers cleanly.

Common Mistakes

Mistake 1 — Comparing Strings With ==

String s1 = new String("hello"); String s2 = new String("hello"); if (s1 == s2) { // false — different objects System.out.println("Equal"); // never prints } if (s1.equals(s2)) { // true — same content System.out.println("Equal"); // prints }

Mistake 2 — Ignoring the Return Value of String Methods

String username = " rohan_mehta "; username.trim(); // result thrown away — username unchanged username.toUpperCase(); // same mistake System.out.println(username); // still " rohan_mehta " // Correct username = username.trim().toUpperCase(); System.out.println(username); // "ROHAN_MEHTA"

Mistake 3 — NullPointerException From Calling Methods on a Null String

String input = null; System.out.println(input.length()); // NullPointerException // Safe pattern — check null first if (input != null && !input.isBlank()) { System.out.println(input.length()); } // Or — put the known value first in equals() boolean isAdmin = "admin".equals(input); // false — no NPE even if input is null

Mistake 4 — Building Strings With + Inside a Loop

// Inefficient — creates a new String object on every iteration String result = ""; for (int i = 1; i <= 10000; i++) { result = result + i + ","; // 10,000 temporary String objects created } // Efficient — one mutable buffer for the whole loop StringBuilder sb = new StringBuilder(); for (int i = 1; i <= 10000; i++) { sb.append(i).append(","); // modifies the same object every time } String result2 = sb.toString();

Interview Questions

Q1. What is a String in Java and why is it immutable?

A String is a sequence of characters represented by the java.lang.String class. It is immutable — once a String object is created, its content cannot be changed. Immutability enables the String pool optimisation, where identical string literals can share the same object in memory without risk of one caller changing the content that another caller is reading. It also makes strings inherently thread-safe and safe to use as HashMap keys, since their hash code never changes.

Q2. What is the String pool in Java?

The String pool is a special region inside the heap where Java stores string literals. When you write String s = "hello", the JVM checks the pool first. If "hello" already exists there, it returns the existing reference — no new object is created. If not, it creates a new entry. This makes string literal reuse memory-efficient. Strings created with new String("hello") bypass the pool and always create a new object. You can add a new String to the pool manually using the intern() method.

Q3. What is the difference between equals() and == for Strings?

== compares references — it returns true only when both variables point to the exact same object in memory. equals() compares content — it returns true when both strings contain the same sequence of characters, regardless of whether they are the same object. Two strings created as literals with the same value may return true for == due to pool reuse, but two strings created with new String(...) containing the same value will return false for ==. Always use equals() for content comparison.

Q4. What is the difference between String, StringBuilder, and StringBuffer?

String is immutable — every operation produces a new object. StringBuilder is mutable and not thread-safe — use it for building strings in single-threaded code, especially inside loops. StringBuffer is mutable and thread-safe — all its methods are synchronised — use it when the same StringBuilder is accessed from multiple threads. For almost all common development scenarios, String for individual values and StringBuilder for building are the right choices. StringBuffer is rarely needed in modern Java where thread-local string building is preferred.

Q5. What does str.split(".") return?

An empty array — not what most beginners expect. split() takes a regular expression, and . in regex means "match any character". So "hello.world".split(".") matches every character and returns an empty array. To split on a literal dot, escape it: str.split("\\."). This is one of the most common beginner traps with split().

Q6. Why should you put the string constant on the left side of equals()?

"constant".equals(variable) is safe even when variable is null — calling .equals() on a non-null constant cannot throw NullPointerException. If you write variable.equals("constant") and variable is null, it throws NullPointerException. Since user input, database values, and API responses can all be null, putting the known non-null constant on the left is a defensive habit that prevents unnecessary null checks.

FAQs

Can you change a String after creating it in Java?

No. A String object's character content is fixed at creation. What you can do is reassign the variable to point to a new String — name = name.toUpperCase() — but the original "priya" object still exists in memory untouched until the garbage collector removes it. If you need to build or modify text frequently, use StringBuilder instead.

Is String a primitive type in Java?

No. String is a class — java.lang.String. It is a reference type, just like ArrayList or any custom class. However, Java gives it special syntax: you can create String literals with double quotes ("hello") without using the new keyword, and you can use + for concatenation. These conveniences make it feel primitive-like, but it is a full object with methods.

What is the difference between length for arrays and length() for Strings?

For arrays, length is a field — no parentheses: arr.length. For Strings, length() is a method — with parentheses: str.length(). Using str.length on a String causes a compile error, and arr.length() on an array also causes a compile error. The distinction exists because arrays are a special Java feature handled differently from objects, while String is a regular class.

What happens when you concatenate a String with null?

When null is concatenated with a String using +, Java converts null to the string "null" — no exception is thrown. So "Hello, " + null produces "Hello, null". However, calling .equals() or any method on a null reference throws NullPointerException.

What is the intern() method in Java?

intern() adds a String to the String pool (or returns the existing pool entry if one already exists with the same content). new String("hello").intern() returns the pooled reference for "hello". After calling intern(), you can compare with == reliably. In practice, intern() is rarely used in application code — it is more relevant in frameworks that process massive volumes of repeated strings.

Summary

String is Java's most used class, and its core behaviours are few and memorable. Strings are immutable — every method returns a new String, never modifying the original. String literals are pooled — identical literals share one object. Always compare with equals(), never ==. Always capture the return value of String methods.

The methods you will use most often are trim(), toLowerCase(), toUpperCase(), contains(), startsWith(), endsWith(), replace(), substring(), indexOf(), split(), equals(), equalsIgnoreCase(), and String.format(). These cover the vast majority of text processing tasks in real-world Java development.

For interviews, be ready to explain immutability and why it exists, describe the String pool, explain the difference between equals() and ==, and explain when to use StringBuilder over +. These appear in nearly every Java interview at every level.

What to Read Next

Practice Programs

Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.

Show all 15 practice programs ↓
Browse all Java programs →