Java Strings – A Beginner’s Guide with Syntax and Examples

Why are Strings immutable in Java? What is the performance impact of String memory allocation? Strings are a part of Java programming, yet many developers still don’t understand how to effectively use these Java Strings.

In Java, a String is an object with a sequence of characters in the form of UTF-16. The Java String class generally contains a rich API for string manipulation and comparison, and concatenation. String manipulation in Java can be inefficient and lead to memory overhead and performance issues. Java even has different string alternatives in the form of StringBuilder and StringBuffer for more efficient string manipulation.

This Java String tutorial generally includes String types, memory management, crucial operations, and best practices so that you may utilize Java Strings efficiently. Let’s begin!

Table of Contents:

What are Strings in Java?

A Java String is generally a string of characters, just presented in the form of an object belonging to the String class. Strings are generally different from primitive data types in the fact that they are immutable in the sense that they cannot be modified after they have been initialized. Such an immutability feature of Java strings simply provides Java programming with security, thread safety, and memory efficiency.

Strings in Java Output

Java provides the String class in the package java.lang, and therefore, it is the core part of the Java language. Strings are generally used in data manipulation and input/output operations, and in the handling of texts, and therefore, it is the most important topic in Java programming.

Main Characteristics of Strings in Java:

  1. Immutable: Java Strings are immutable in nature in the sense that once a String object is created, the value of the object cannot be changed or modified. Any modification will only cause the creation of a new object.
  2. Stored in the String Pool: Java saves memory by maintaining string literals in a particular area of memory called the String pool.
  3. Implementations CharSequence: The String class in Java implements the CharSequence interface and hence is in harmony with the other character classes like StringBuilder and StringBuffer.
  4. Supports Varying Operations: Java primarily supplies various built-in string manipulation methods, including concat(), substring(), replace(), split(), and many others.

Why are Strings Important in Java?

String in Java is the most frequently used object in the Java Programming Language. They are predominantly used in user input handling, database handling, file handling, and web applications. Understanding how they work improves performance, reduces memory leaks, and prevents Java programming errors.

Ways to Create Strings in Java

There are two significant ways to instantiate Strings in Java:

  1. Using String Literals (Stored in the String Pool)
  2. Utilizing the new Keyword (Saved in Heap Memory)

Each has very different memory and performance implications, ones that we must learn in order to program Java effectively.

1. Using String Literals (Stored in the String Pool)

A string literal is generally the most common way in which a String in Java is declared. If a String is declared simply with the use of double quotes (” “), Java will check first if the same value is already in the String pool or not. If it is there, Java just reuses the same object instead of creating a new object.

Example:

Java

Output:

Using String Literals Output

How It Works:

  • The JVM maintains a separate memory area known as the String pool (on the heap) in which it keeps unique string literals.
  • Upon “Hello, Java” generation, Java looks into the pool:
    • If it is in a string pool, a reference to the present object is returned.
    • If not, then a new object is created and stored in the string pool.
  • The == operator here is testing if the pointers both refer to the same object.

Advantages of String Literals:

  • Memory-efficient: The String literal approach in string creation just avoids object duplications by reusing the ones already present.
  • Performance-enhanced: It also provides faster access because it employs interned Strings from the pool.

When to Use String Literals?

  • Apply this technique when memory should be saved and current string values should be reused.
  • It is quite useful for the majority of String operations where immutability is required.

2. Using new Keyword (Stored in Heap Memory)

If you use the new keyword to create any String object, then Java deliberately creates a new object in heap memory even if there is already a string with the same content in the String pool.

Example:

Java

Output:

Using new Keyword Output

How It Works:

  • The new keyword in the code simply creates a new String object in the heap memory, even though “Hello, Java” is already present in the String pool.
  • The == operator is false since both str1 and str2 are pointing to different memory objects.
  • The String can be stored in the String pool if intern() is called.

Disadvantages of Using new for Strings:

  • Consumes more memory: Has a tendency to make a copy object on the heap.
  • Slower performance: It also required additional object creation and garbage collection.

When to Use?

  • Use this technique when you need an independent String object, although it is not often needed in real applications.
  • If there are numerous changes to Strings, it’s usually best to use StringBuilder or StringBuffer.

3. Using .intern() Method to Store in the String Pool

If we create a String with new, but we simply want to place it in the String pool, we simply use the .intern() method.

Example:

Java

Output:

Using String Literals OutputHow It Works:

  • .intern() looks into the String pool:
    • If the value already exists, it just returns a reference to the object already present.
    • If not, it puts it into the pool and returns the pointer.
  • This facilitates optimal use of memory without the loss of control over String memory.

When to Use?

  • Use the intern() method when you have dynamic Strings to be stored in the String pool for the purpose of optimization.
  • This technique is quite useful in minimizing memory consumption in programs that produce numerous duplicate Strings.

Comparing Both Approaches – String Literals vs new Keyword

Creation Method Memory Location Performance Object Reuse Best Use Case
String Literal (“”) String Pool (Heap) Fastest Yes (if value exists) Best for static, frequently used Strings
new Keyword Heap (outside String Pool) Slower No (Creates a new object) Rarely needed, only for unique instances
intern() Method Moves to String Pool Medium Yes (Moves the object) Useful for reducing memory usage with dynamic Strings

Types of Strings in Java

Java strings can be categorized into different types on the basis of mutability, memory storage, and usage. Knowing these types will lead you to the right approach in terms of performance, memory, and thread-safety issues.

1. Immutable Strings

An immutable String or standard string is a string whose value remains unaltered once it’s created. Any change results in the creation of a new String object rather than the modification of the existing object.

Example:

Java

Output:

Immutable Strings Output

Why are Strings Immutable in Java?

  • Security: String’s Immutability in Java prevents any accidental alteration of sensitive data like passwords, class loading, and network connections.
  • Performance Improvement: It enhances the overall performance of the system since JVM reuses the objects through String pooling.
  • Thread-safety: Because you will not be modifying the Strings, they are thread-safe without synchronization.

2. Mutable Strings

Since we have discussed that strings are immutable and changing them may create new objects and hence cause unnecessary memory consumption. To solve this problem in Java, we have StringBuilder and StringBuffer available, and they may be used to modify Strings without creating new objects.

2.1 StringBuilder (Faster, Not Thread-Safe)

The Java StringBuilder class generally facilitates dynamic String manipulation. It is not thread-safe but is faster than StringBuffer.

Example:

Java

Output:

StringBuilder Output

Why Use StringBuilder?

  • StringBuilder is faster in most cases if there are multiple modifications or changes required.
  • There is no object creation on change (memory use reduces).
  • This is the default choice for usage in single-threaded applications.

2.2 StringBuffer (Thread-Safe but Slower)

StringBuffer is equivalent to StringBuilder but is thread-safe and may be utilized in multithreaded programs.

Example:

Java

Output:

StringBuilder Output

Why Use StringBuffer?

  • Thread safe: StringBuffer can be utilized in multithreaded applications with ease.
  • They are optimized but slightly slower than StringBuilder due to synchronization.

Comparison: String vs StringBuilder vs StringBuffer

Feature String (Immutable) StringBuilder (Mutable) StringBuffer (Mutable & Thread-Safe)
Mutability No Yes Yes
Performance Slow (New object creation) Fast Slower than StringBuilder
Thread-Safety Yes (Safe by default) No Yes
Best Use Case Constant text values Single-threaded apps Multi-threaded apps

CharSequence Interface in Java

The CharSequence interface is the root interface for any char-sequence operations in Java. It is a super interface of String, StringBuilder, and StringBuffer, which generally provides a common way of representation and processing of text information.

It is used by:

  • String (Immutable)
  • StringBuilder (Mutable)
  • StringBuffer (Mutable, Thread-safe)
  • CharBuffer (For memory-efficient character sequences)

Why Use CharSequence?

  • The CharSequence generally provides a common way to handle different forms of text representation.
  • It allows interchangeability between different string-handling classes.
  • It also saves memory overhead while handling large text-based data.

1. Methods in CharSequence Interface

The CharSequence interface typically offers easy read-only operations that must be implemented by every implementing class

Method Description
charAt(int index) Returns the character at a specific index.
length() Returns the number of characters in the sequence.
subSequence(int start, int end) Extracts a portion of the sequence.
toString() Converts the sequence into a String.

2. Implementations of CharSequence

Since CharSequence is an interface, we cannot instantiate it directly, although we can instantiate its implementing classes like String, StringBuilder, StringBuffer, and CharBuffer.

Example 1: Using CharSequence with Different Implementations

Java

Output:

CharSequence with Different Implementations

3. Difference Between CharSequence and String

Feature CharSequence String
Type Interface Class
Mutability Can be mutable or immutable Immutable
Implementations String, StringBuilder, StringBuffer, CharBuffer Directly instantiated
Memory Efficiency Can be optimized using custom implementations Uses String pool
Use Case Generic text handling Constant text values

Strings in Character Arrays (char[])

Unlike String in Java, character arrays (char[]) can be changed by single characters. This is very useful for security-sensitive programs like password handling.

Java

Output:

Strings in Character Arrays Output

Concept of Memory Allocation of Strings in Java

Concept of Memory Allocation of Strings in Java

It is very important for you to understand the memory allocation of Java Strings in order to develop efficient programs. Since Strings are immutable, their storage and retrieval are optimized by Java through mechanisms like the String Pool and different regions of heap memory.

1. Where are Strings Stored in Java?

Generally, Java uses two main memory spaces to store String objects:

  • String Pool (String Interning): It resides in Heap Memory
  • Heap Memory: This memory typically stores Normal String Objects
  • Stack Memory: This memory stores String references

2. How are Strings Allocated in Memory?

a) String Pool (Interned Strings)

The Java String Pool is a special area in the heap where string literals are typically stored in order to avoid memory loss.

How it works?

  • Whenever you create any String with double quotes (” “), Java first checks if the value is already in the pool.
  • If already discovered, the same reference is returned.
  • Otherwise, a new object is instantiated and inserted into the pool.

Example: String Pool Behavior

Java

Output:

String Pool Behavior

Key Takeaways:

  • “Java” is placed in the String Pool only once and shared for both s1 and s2.
  • s3 here is built using new, so it does not utilize the pool and gets a new memory allocation.

b) Heap Memory (Outside String Pool)

If a String is being created by invoking new String(“value”), it is stored outside the String Pool, straight into the Heap Memory.

Why use new String()?

  • It simply disallows string interning in case we need a distinct instance.
  • It is very Helpful in situations that need unique string objects, such as cryptographic applications.
String s1 = new String("Hello");  // Stored in Heap

String s2 = "Hello";  // Stored in the String Pool

System.out.println(s1 == s2); // false (Different memory locations)

Best Practice: Avoid new String(“value”) unless you need to save memory.

c) Stack Memory (Stores References to Strings)

Stack memory in Java cannot store string objects, but it stores references (pointers) to objects created in the heap or String Pool.

3. Why Did the String Pool Move from PermGen to Heap?

Earlier (Java 6 and below), the String Pool was stored in the PermGen (Permanent Generation), which is a size-limited area.

Problems with PermGen Storage:

  • Finite memory (fixed-size space) 64 MB on 32-bit and 82 MB on the 64-bit JVM version.
  • OutOfMemoryError (OOM) if too many strings are created.

Solution in Java 7+: Moved String Pool to Heap

  • Heap memory is larger and can be garbage-collected efficiently.
  • Allows for improved performance and scalability.

4. Best Practices for Memory Optimization in Java using Strings

  • Use “string” directly instead of new String(“string”) to take advantage of the String Pool.
  • Use intern() for duplicate strings in large-scale applications.
  • Use StringBuilder for manipulation instead of String in order not to create unnecessary objects.
  • Enable optimizations for memory management using Garbage Collection.

Operations on String in Java

Almost every Java program, from console programs to business programs, uses string manipulation to a large extent. Java contains a rich collection of inbuilt methods in order to deal with frequent string operations in a very effective manner. It is necessary to be aware of these operations for the handling of the following:

Here we will be discussing the 7 most commonly utilized string operations in Java and the way they actually function internally so that you can write bug-free and efficient code.

1. Concatenation

Concatenation is the operation of merging multiple strings into a string. Three major types of concatenation are commonly implemented in Java:

  • Using the + operator (simple but not very effective).
  • Using the concat() function (more readable but equivalent to +).
  • Using StringBuilder (most effective in repetitive concatenation).

a. Using + Operator

The + operator is the simplest and direct string concatenation form. It does create multiple objects in memory and therefore causes performance issues in big applications.

Java

Output:

Using + Operator Output

Performance Issue:

  • A new String object is created every time we use +.
  • This will use more memory and is even slower, especially in loops.

b. Using concat() Method

The concat() method in Java simply concatenates two strings without changing the original string.

Java

Output:

Using concat() Method Output

Key Points:

  • The same as the disadvantages of +, as it constructs a new string instead of modifying the existing one.

c. Using StringBuilder

StringBuilder is mutable, i.e., it modifies the same object instead of creating new objects.

Java

Output:

Using StringBuilder Output

Why Use StringBuilder?

  • They are much faster than + and concat() when concatenating strings inside loops.
  • They never form duplicate objects and reduce the use of memory.
  • Heavily used in real-time applications like handling large files and logs

2. Substring Extraction

A substring in Java is generally a subset of a string. The substring() function allows you to access subsets of a string in an efficient way.

Java

Output:

Substring Extraction

Key Points:

  • Indexing begins at 0.
  • The end index is an exclusive index, and it will not include the character in the last index.

Performance Tip:

  • In previous versions of Java (before Java 7), substring() method used to share the same memory reference.
  • Post-Java 7, it does create a new object, so it’s safer but a little memory-hungry.

3. String Length

To find the length of a string, the function length() can be used, and it will return the total length and size of the string.

Java

Output:

String Length

This operation also covers special characters, spaces, and punctuation.

4. Extracting Characters (charAt())

The charAt(index) function enables you to find a specific character in a string.

Java

Output:

Extracting Characters

Warning:

  • If the index is outside the range, Java simply throws StringIndexOutOfBoundsException.

5. String Comparison

Comparison between strings in Java is a very important operation. Different methods produce different behaviors:

a. Using equals()

Java

Output:

Using equals()

Returns true if and only if the strings are equal.

b. Using equalsIgnoreCase() (Overlooking Case Differences)

Java

Output:

Using equals()

This method is very convenient for user authentication, where there is no requirement for case sensitivity.

c. Using compareTo() (Lexicographical Comparison)

Java

Returns:

  • 0 -> if both strings are equal.
  • -1 -> if s1 < s2.
  • +1 -> if s1 > s2.

Output:

Using compareTo() (Lexicographical Comparison)

6. String Case Conversion

This operation will primarily convert uppercase Strings to lowercase or vice versa.

Java

Output:

String Case Conversion

It is useful for case-insensitive search and validation of user inputs.

7. Finding a Character or Substring

a. Verifying if a String Contains a Specific Word

Java

Output:

Using equals()

Returns true if found, else false.

b. Finding the First Occurrence of a Substring (indexOf())

Java

Output:

Finding the First Occurrence of a Substring

Returns -1 if not found.

Strings Methods in Java

Below is a table of the most used String methods in Java for your reference:

Method Description Example Usage
length() Returns the length of the string. “Java”.length() -> 4
charAt(index) Retrieves the character at a specified index. “Java”.charAt(1) -> ‘a’
substring(start, end) Extracts a substring from start to end (exclusive). “Hello”.substring(1, 4) -> “ell”
contains(seq) Checks if a sequence of characters is present. “Java is fun”.contains(“Java”) -> true
equals(str) Compares two strings (case-sensitive). “Java”.equals(“java”) -> false
equalsIgnoreCase(str) Compares strings, ignoring case. “Java”.equalsIgnoreCase(“java”) -> true
toLowerCase() Converts all characters to lowercase. “HELLO”.toLowerCase() -> “hello”
toUpperCase() Converts all characters to uppercase. “hello”.toUpperCase() -> “HELLO”
trim() Removes leading and trailing spaces. ” Java “.trim() -> “Java”
replace(old, new) Replaces occurrences of a character or substring. “abc”.replace(‘a’, ‘z’) -> “zbc”
split(regex) Splits the string based on a regex pattern. “a,b,c”.split(“,”) -> [“a”, “b”, “c”]
startsWith(prefix) Checks if the string starts with a specific prefix. “Java”.startsWith(“J”) -> true
endsWith(suffix) Checks if the string ends with a specific suffix. “Java”.endsWith(“a”) -> true
indexOf(str) Finds the first occurrence of a substring. “hello”.indexOf(“l”) -> 2
lastIndexOf(str) Finds the last occurrence of a substring. “hello”.lastIndexOf(“l”) -> 3
isEmpty() Checks if the string is empty. “”.isEmpty() -> true
concat(str) Concatenates two strings. “Java”.concat(” Programming”) -> “Java Programming”

Best Practices for Using Strings in Java

  • Use StringBuilder if you want to manipulate strings instead of String because String is immutable, and it creates new objects with every manipulation.
  • Use strings with.equals(), never ==, because == will compare object references and not the content itself.
  • Don’t use + in a loop to concatenate strings because it creates a lot of unnecessary String objects. Use StringBuilder.append() instead.
  • Use toLowerCase(Locale.ROOT) and toUpperCase(Locale.ROOT) to avoid locale-sensitive case conversion differences.
  • Prefer strip() over trim() (Java 11+) since it handles Unicode whitespace more correctly. Use stripLeading() and stripTrailing() where they apply.
  • Use intern() on strings that are used multiple times so they are stored in the String Pool and save memory.

Conclusion

With this, we have concluded this Java Strings Tutorial, where you have learned that String is a core element of Java and is very important for data manipulation and text processing. You have learned about String creation, types of strings, memory allocation, crucial methods, and tips for performance enhancement. Immutability, String Pool, and efficient operations are the most crucial concepts to write high-performance Java code. With the help of this Java String Tutorial, you can write cleaner, quicker, and effective Java code and avoid common issues.

Java Strings - FAQs
1. Why is String immutable in Java?

Java String is immutable because of security, caching, synchronization, and performance. It prevents accidental change, so it is safe to use in hashing, class loading, and multithreading.

2. What is the difference between == and .equals() in Java Strings?
  • == checks for reference equality (whether two string objects point to the same memory location).
  • equals() checks for content equality (whether two strings have the same characters).
3. How does the Java String Pool work?

String Pool is a special area of memory in the heap where string literals are stored. Every time a string is created using the String literal, Java first examines if it is already present in the pool or not. If it is, then the reference is reused, which conserves memory.

4. When should I use StringBuilder instead of String?

Use StringBuilder when you are performing frequent string manipulation, i.e., concatenation in loops. Since String is immutable, its manipulation creates new objects, leading to memory overhead and performance issues.

5. Why did the String Pool move from PermGen to the heap in Java 7?

Starting with Java 7, the String Pool was moved from PermGen to the heap to avoid OutOfMemoryErrors and enable garbage collection more efficiently, improving the memory management of dynamic applications.

About the Author

Technical Research Analyst - Full Stack Development

Kislay is a Technical Research Analyst and Full Stack Developer with expertise in crafting Mobile applications from inception to deployment. Proficient in Android development, IOS development, HTML, CSS, JavaScript, React, Angular, MySQL, and MongoDB, he’s committed to enhancing user experiences through intuitive websites and advanced mobile applications.