Strings

A String is text — a name, an address, a sentence pulled from a form field. It's technically not a primitive type but a full object, and that one fact explains most of the quirks you'll run into while working with it.

Strings are objects, and objects are immutable here

Once a String is created, it can never be changed. Every method that looks like it's "modifying" a string — toUpperCase(), trim(), replace() — is actually building and returning a brand new String, leaving the original untouched.

</> ImmutableDemo.java
String original = "hello";
String shouted = original.toUpperCase();

System.out.println(original);
System.out.println(shouted);
Output
hello
HELLO

original still says "hello" — calling toUpperCase() didn't touch it, it handed back a separate String that we stored in shouted. If you forget this and write original.toUpperCase(); on its own line expecting original to change, nothing will happen, and there won't be an error to tell you why.

Building and combining strings

The + operator concatenates strings, and Java will automatically convert numbers and booleans to text when they're mixed into a concatenation:

</> Invoice.java
public class Invoice {
    public static void main(String[] args) {
        String customer = "Dana Whitfield";
        int itemCount = 3;
        double total = 84.50;

        String summary = customer + " ordered " + itemCount + " items, total $" + total;
        System.out.println(summary);
    }
}
Output
Dana Whitfield ordered 3 items, total $84.5

Notice $84.5, not $84.50 — Java doesn't know this number represents money, so it drops the trailing zero like any other double. Formatting currency properly needs something like String.format("%.2f", total), which is worth knowing exists even before you need the full details of it.

Useful String methods

A String comes with a long list of built-in methods. These four cover the majority of everyday work:

  • .length() — the number of characters. Note it's a method with parentheses, unlike an array's .length field.
  • .charAt(index) — the single character at a position, counting from zero.
  • .substring(start, end) — a slice of the string, from start up to (but not including) end.
  • .indexOf(text) — the position where text first appears, or -1 if it's not found anywhere.
</> EmailParser.java
public class EmailParser {
    public static void main(String[] args) {
        String email = "steve.watson@nutriessential.com";
        int atIndex = email.indexOf('@');

        String username = email.substring(0, atIndex);
        String domain = email.substring(atIndex + 1);

        System.out.println("Username: " + username);
        System.out.println("Domain: " + domain);
        System.out.println("Length: " + email.length());
    }
}
Output
Username: steve.watson
Domain: nutriessential.com
Length: 32

Comparing strings the right way

This is the one habit worth locking in early: use .equals() to compare string content, never ==.

</> CompareDemo.java
String input = new String("yes");
String target = "yes";

System.out.println(input == target);
System.out.println(input.equals(target));
Output
false
true

input and target hold the same text, but new String("yes") forces Java to create a distinct object in memory, so == — which checks whether two variables point at the identical object — says false. .equals() looks at the actual characters instead, and correctly says true. In everyday code you rarely write new String(...) yourself, but the underlying rule always applies: == on any object type, strings included, is an identity check, not a content check.

Note: if you're building a string piece by piece inside a loop — say, assembling a report line by line — repeated + concatenation creates a new String object on every single pass, which gets wasteful fast. StringBuilder exists for exactly that situation: it can grow and change in place, and you call .toString() once at the end to get your finished String back.