Łańcuch znaków w Javie, czyli String, to obiekt przechowujący ciąg znaków Unicode, na przykład "Automatyzacja". Nie jest typem prostym jak int, tylko obiektem klasy java.lang.String, a jego treści po utworzeniu nie da się zmienić.
To dwunasta lekcja kursu Javy dla testerów. Pod filmem znajdziecie pełną lekcję tekstową: jak tworzyć łańcuchy znakowe, dlaczego == i equals dają różne wyniki, kilkanaście najczęściej używanych metod z prawdziwymi wynikami, pułapki z Unicode i spacjami oraz Stringi w asercjach JUnit 6 i AssertJ. Każdy przykład skompilowaliśmy poleceniem javac --release 25 i uruchomiliśmy, a wyniki w komentarzach skopiowaliśmy z konsoli.
Spis treści
- Czym jest łańcuch znaków w Javie?
- Jak utworzyć String: literał, new i blok tekstu
- Co znaczy, że String jest niezmienny?
- Porównywanie łańcuchów znakowych: == kontra equals
- Jakie metody klasy String trzeba znać?
- Jak łączyć łańcuchy znakowe?
- Jakie pułapki kryje Unicode?
- Stringi w asercjach: JUnit 6 i AssertJ
- Jakie błędy zgłosi kompilator?
- Ćwiczenie z rozwiązaniem
Czym jest łańcuch znaków w Javie?
Łańcuch znakowy to uporządkowany ciąg znaków. W Javie reprezentuje go wbudowana klasa String, więc nie musicie niczego importować. Według dokumentacji API Java SE 25 (Oracle, 2025) każdy literał w cudzysłowie, na przykład "abc", jest obiektem tej klasy. Ta sama dokumentacja mówi wprost, że wartości łańcuchów są stałe i nie zmieniają się po utworzeniu.
W pracy testera Stringi są wszędzie. Komunikat błędu w formularzu, tytuł strony, adres URL, treść odpowiedzi API, nazwa przeglądarki z pliku konfiguracyjnego. Każda z tych rzeczy trafia do testu jako String. Dlatego ta lekcja jest jedną z najbardziej praktycznych w całym kursie.
Łańcuchy znakowe w Javie w liczbach
1 bajt
na znak od Javy 9 dzięki Compact Strings, ale tylko gdy cały tekst mieści się w Latin-1; jedno „ą” przełącza cały String na 2 bajty
15
wersja Javy, w której bloki tekstu “”” weszły na stałe
25
wersja LTS, na której sprawdziliśmy wszystkie przykłady z tej lekcji
450 000+
testów automatycznych napisanych w Quality Island
Źródła: OpenJDK JEP 254, 2017; OpenJDK JEP 378, 2020; Oracle Java SE Support Roadmap, 2023; dane własne Quality Island 2026.
Jak utworzyć String: literał, new i blok tekstu
Najczęściej tworzycie String literałem w cudzysłowie. Konstruktor new String(...) działa, ale w zwykłym kodzie nie ma powodu go używać, bo zawsze tworzy nowy obiekt. Liczbę zamienicie na tekst metodą String.valueOf. Dłuższy tekst, na przykład JSON do testu API, wygodnie zapiszecie blokiem tekstu w potrójnym cudzysłowie.
Tworzenie.java, wynik w komentarzach
public class Tworzenie {
public static void main(String[] args) {
String literal = "Automatyzacja";
String zKonstruktora = new String("Automatyzacja");
String liczba = String.valueOf(450000);
String json = """
{
"login": "tester",
"rola": "QA"
}
""";
System.out.println(literal.length()); // 13
System.out.println(zKonstruktora); // Automatyzacja
System.out.println(liczba + 1); // 4500001
System.out.print(json);
}
}
Zwróćcie uwagę na trzecią linię wyniku. liczba + 1 to sklejenie tekstu, a nie dodawanie, więc wychodzi 4500001. Blok tekstu z JEP 378 (OpenJDK, 2020) usuwa wspólne wcięcie i zachowuje złamania linii. W testach API oszczędza to dziesiątki znaków \n i \".
Co znaczy, że String jest niezmienny?
Niezmienność oznacza, że żadna metoda klasy String nie zmienia obiektu, na którym ją wywołujecie. Metody takie jak toUpperCase, replace czy strip zwracają nowy obiekt. Jeśli wyniku nie przypiszecie do zmiennej, przepada.
Niezmiennosc.java, wynik w komentarzach
public class Niezmiennosc {
public static void main(String[] args) {
String status = "pending";
status.toUpperCase();
System.out.println(status); // pending
String nowy = status.toUpperCase();
System.out.println(nowy); // PENDING
status = status.toUpperCase();
System.out.println(status); // PENDING
}
}
To jeden z najczęstszych błędów w kodzie testów, który widzimy u kursantów. Ktoś pisze tekst.strip();, a potem porównuje nieprzycięty tekst i dziwi się, że test nie przechodzi. String jest jak dobrze napisany wymóg w specyfikacji: raz zatwierdzony, nikt go już po cichu nie zmienia.
Niezmienność ma też zalety. Według dokumentacji API Java SE 25 (Oracle, 2025) właśnie dzięki niej obiekty String można bezpiecznie współdzielić. Na tym opiera się pula łańcuchów, o której za chwilę.
Porównywanie łańcuchów znakowych: == kontra equals
Operator == sprawdza, czy dwie zmienne wskazują ten sam obiekt w pamięci. Metoda equals sprawdza, czy dwa łańcuchy mają tę samą treść. W testach prawie zawsze chodzi Wam o treść, więc porównujcie przez equals.

Dlaczego == czasem „działa”? Specyfikacja języka Java SE 25 (Oracle, 2025, rozdział 3.10.5) mówi, że identyczne literały wskazują ten sam obiekt w puli łańcuchów. Stałe sklejone przy kompilacji też trafiają do puli. Tekst sklejony w czasie działania programu już nie, więc == zwróci false, choć treść jest identyczna.
Porownania.java, wynik w komentarzach
import java.util.Objects;
public class Porownania {
public static void main(String[] args) {
String a = "java";
String b = "java";
String c = new String("java");
String d = "ja" + "va";
String czesc = "ja";
String e = czesc + "va";
System.out.println(a == b); // true, ten sam obiekt z puli
System.out.println(a == c); // false, nowy obiekt
System.out.println(a.equals(c)); // true, ta sama treść
System.out.println(a == d); // true, stała obliczona przy kompilacji
System.out.println(a == e); // false, sklejone w czasie działania
System.out.println(a == e.intern()); // true
System.out.println("java".equalsIgnoreCase("Java")); // true
String zOdpowiedzi = null;
System.out.println("OK".equals(zOdpowiedzi)); // false, bez wyjątku
System.out.println(Objects.equals(zOdpowiedzi, null)); // true
// zOdpowiedzi.equals("OK") rzuciłoby NullPointerException
}
}
Źródło: Oracle, dokumentacja API Java SE 25: klasy String i Objects, 2025; Java Language Specification SE 25, rozdział 3.10.5.

Poprzednia wersja tej lekcji miała tu błąd: przy equalsIgnoreCase("java", "Java") podawała wynik „a nie jest równe b”. Poprawny wynik to true, bo metoda ignoruje wielkość liter. Sprawdziliśmy to w konsoli, wynik widzicie w kodzie wyżej.
Jakie metody klasy String trzeba znać?
Klasa String ma kilkadziesiąt metod, ale w testach regularnie wracacie do kilkunastu. Poniżej te, które warto znać na pamięć, na jednym przykładzie "Automatyzacja". Indeksy liczymy od zera, więc piąty znak ma indeks 4.
Metody.java, wynik w komentarzach
import java.util.Arrays;
public class Metody {
public static void main(String[] args) {
String x = "Automatyzacja";
System.out.println(x.length()); // 13
System.out.println(x.charAt(4)); // m
System.out.println(x.substring(0, 4)); // Auto
System.out.println(x.substring(4)); // matyzacja
System.out.println(x.indexOf("a")); // 5
System.out.println(x.lastIndexOf("a")); // 12
System.out.println(x.indexOf("z")); // 8
System.out.println(x.indexOf("Q")); // -1
System.out.println(x.contains("mat")); // true
System.out.println(x.startsWith("Auto")); // true
System.out.println(x.endsWith("cja")); // true
String komunikat = "Programowanie Java";
System.out.println(komunikat.replace("Java", "C++")); // Programowanie C++
System.out.println(komunikat.toUpperCase()); // PROGRAMOWANIE JAVA
System.out.println(komunikat.toLowerCase()); // programowanie java
String pole = " tester@qualityisland.pl ";
System.out.println("[" + pole.strip() + "]"); // [tester@qualityisland.pl]
System.out.println("".isEmpty()); // true
System.out.println(" ".isEmpty()); // false
System.out.println(" ".isBlank()); // true
String csv = "Chrome;Firefox;Edge";
String[] przegladarki = csv.split(";");
System.out.println(Arrays.toString(przegladarki)); // [Chrome, Firefox, Edge]
System.out.println(String.join(", ", przegladarki)); // Chrome, Firefox, Edge
System.out.println("=".repeat(10)); // ==========
}
}

Źródło: Oracle, dokumentacja API Java SE 25, klasa String, 2025. isBlank, strip i repeat są dostępne od Javy 11.

Metoda repeat to mały skarb dla testera. Test wartości brzegowych dla pola z limitem 255 znaków piszecie jako "a".repeat(255) i "a".repeat(256), zamiast wklejać do kodu ścianę liter. Ta sama technika przydaje się w testach API, gdy sprawdzacie, jak serwer reaguje na zbyt długi parametr.
Uwaga na poprzednią wersję tej lekcji. Kod "Programowania Java".replace("Java", "C++") zwraca "Programowania C++", a nie „Programowanie C++”, bo replace podmienia tylko wskazany fragment. Reszta tekstu zostaje bez zmian, łącznie z literówką.
Jak łączyć łańcuchy znakowe?
Łączenie łańcuchów to konkatenacja. Macie do wyboru cztery narzędzia: operator +, String.format albo formatted, StringBuilder oraz String.join. Każde ma swoje miejsce.
Laczenie.java, wynik w komentarzach
import java.util.List;
public class Laczenie {
public static void main(String[] args) {
String jezyk = "Java";
String pelny = "Programowanie" + " " + jezyk;
System.out.println(pelny); // Programowanie Java
String raport = String.format("Test %s: %d z %d zaliczonych", "logowanie", 9, 10);
System.out.println(raport); // Test logowanie: 9 z 10 zaliczonych
System.out.println("Czas: %.2f s".formatted(1.5)); // Czas: 1,50 s przy polskich ustawieniach systemu
List<String> kroki = List.of("otwórz stronę", "wpisz login", "kliknij Zaloguj");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < kroki.size(); i++) {
sb.append(i + 1).append(". ").append(kroki.get(i)).append('\n');
}
System.out.print(sb);
System.out.println(String.join(" > ", kroki)); // otwórz stronę > wpisz login > kliknij Zaloguj
}
}
Operator + wystarczy do kilku elementów, a od Javy 9 kompilator sam optymalizuje takie sklejanie (OpenJDK JEP 280, 2017). StringBuilder wybierajcie w pętli, bo każde += na zwykłym Stringu tworzy nowy obiekt. String.join skleja listę z separatorem w jednej linii.
Zwróćcie uwagę na wynik formatted. Na komputerze z polskimi ustawieniami wyszło 1,50 z przecinkiem, a na serwerze CI z ustawieniami angielskimi wyjdzie 1.50 z kropką. Test, który porównuje taki tekst, przejdzie lokalnie i padnie w pipeline. Jeśli format liczby ma być stały, podajcie lokalizację wprost: String.format(Locale.ROOT, ...).
Jakie pułapki kryje Unicode?
String w Javie przechowuje tekst w kodowaniu UTF-16. Dla polskich liter wszystko działa intuicyjnie, ale trzy sytuacje regularnie psują testy: znaki spoza podstawowego zakresu Unicode, zmiana wielkości liter zależna od języka systemu i niestandardowe spacje kopiowane z HTML.
Pulapki.java, wynik w komentarzach
import java.util.Locale;
public class Pulapki {
public static void main(String[] args) {
String klucz = "\uD834\uDD1E"; // znak klucza wiolinowego, U+1D11E
System.out.println(klucz.length()); // 2
System.out.println(klucz.codePointCount(0, klucz.length())); // 1
System.out.println("TITLE".toLowerCase(Locale.forLanguageTag("tr"))); // tıtle
System.out.println("TITLE".toLowerCase(Locale.ROOT)); // title
String zTabela = "\u2003Zaloguj"; // spacja typu em z HTML
System.out.println(zTabela.trim().equals("Zaloguj")); // false
System.out.println(zTabela.strip().equals("Zaloguj")); // true
String twarda = "\u00A0Zaloguj"; // twarda spacja
System.out.println(twarda.strip().equals("Zaloguj")); // false
System.out.println(twarda.replace('\u00A0', ' ').strip().equals("Zaloguj")); // true
}
}
Wbrew pozorom
Wywołanie "TITLE".toLowerCase() na komputerze z tureckimi ustawieniami zwraca "tıtle" z literą i bez kropki. Ten przykład podaje sama dokumentacja API Java SE 25 (Oracle, 2025) i zaleca toLowerCase(Locale.ROOT) dla identyfikatorów, kluczy protokołów i znaczników HTML. Test porównujący nagłówki HTTP może więc działać u Was, a paść u kolegi z Izmiru.
Dwie praktyczne zasady na koniec tej sekcji. Po pierwsze, length() liczy jednostki UTF-16, a nie znaki widoczne na ekranie, więc limit znaków w polu sprawdzajcie przez codePointCount. Po drugie, strip usuwa więcej rodzajów białych znaków niż trim, ale twardej spacji nie usuwa żadna z nich. Tekst odczytany z przeglądarki warto najpierw znormalizować.
Stringi w asercjach: JUnit 6 i AssertJ
Asercja na Stringu to najczęstsza asercja w testach UI i API. Sprawdzacie tytuł strony, komunikat walidacji, numer zamówienia z potwierdzenia. Poniżej klasa produkcyjna i pięć testów: dwa w czystym JUnit 6 i trzy z biblioteką AssertJ. Projekt Maven z JUnit 6.1.3 i AssertJ 3.27.7 uruchomiliśmy poleceniem mvn test: 5 testów, 0 błędów.
src/main/java/Komunikaty.java
public class Komunikaty {
static String powitanie(String imie) {
return "Witaj, " + imie.strip() + "! Masz 3 nowe zamówienia.";
}
static String numerZamowienia(String komunikat) {
return komunikat.replaceAll(".*nr (QI-\\d{6}).*", "$1");
}
}
src/test/java/KomunikatyTest.java
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class KomunikatyTest {
@Test
void powitanieJUnit() {
String wynik = Komunikaty.powitanie(" Anna ");
assertEquals("Witaj, Anna! Masz 3 nowe zamówienia.", wynik);
assertTrue(wynik.contains("Anna"), "Powitanie powinno zawierać imię");
}
@Test
void powitanieAssertJ() {
String wynik = Komunikaty.powitanie(" Anna ");
assertThat(wynik)
.startsWith("Witaj, Anna")
.contains("3 nowe zamówienia")
.endsWith(".")
.doesNotContain(" ");
}
@Test
void numerZamowieniaZKomunikatu() {
String komunikat = "Dziękujemy! Zamówienie nr QI-004512 zostało przyjęte.";
assertThat(Komunikaty.numerZamowienia(komunikat))
.isEqualTo("QI-004512")
.matches("QI-\\d{6}");
}
@Test
void komunikatBleduBezWzgleduNaWielkoscLiterIBiale() {
String zUi = " Nieprawidłowy LOGIN lub hasło ";
assertThat(zUi).isEqualToIgnoringWhitespace("Nieprawidłowy LOGIN lub hasło");
assertThat(zUi).containsIgnoringCase("login");
assertThat(zUi).isEqualToNormalizingWhitespace("Nieprawidłowy LOGIN lub hasło");
}
@Test
void pustePoleFormularza() {
assertThat(" ").isBlank();
assertThat("").isEmpty();
assertThat((String) null).isNullOrEmpty();
}
}
AssertJ ma dwie przewagi przy tekstach. Asercje łączycie w łańcuch, więc jeden test czyta się jak zdanie. Ma też gotowe metody, które w JUnit trzeba pisać ręcznie: containsIgnoringCase, isEqualToIgnoringWhitespace, isEqualToNormalizingWhitespace czy matches z wyrażeniem regularnym. Pełną listę znajdziecie w dokumentacji AssertJ.
Druga przewaga to czytelny komunikat porażki. Gdy w kodzie zostanie spacja przed wykrzyknikiem, AssertJ pokaże obie wersje jedna pod drugą:
Wynik mvn test przy celowo zepsutym tekście
org.opentest4j.AssertionFailedError:
expected: "Witaj, Anna! Masz 3 nowe zamówienia."
but was: "Witaj, Anna ! Masz 3 nowe zamówienia."
Źródło: JUnit User Guide, rozdział Assertions; AssertJ Core documentation; sprawdzone na JUnit 6.1.3 i AssertJ 3.27.7, 11.10.2026.
W naszych projektach automatyzacji trzymamy się prostej reguły: assertTrue(tekst.contains(...)) zamieniamy na asercję AssertJ, bo przy porażce assertTrue mówi tylko „expected true, was false”. Jeśli piszecie testy w Selenium, ten sam wzorzec znajdziecie w artykule o asercjach w testach automatycznych, a cały przegląd narzędzi w tekście o Selenium i pierwszych krokach w automatyzacji.
Jakie błędy zgłosi kompilator?
Poprzednia wersja tej lekcji miała kilka literówek, które nie przechodziły kompilacji. Zamieniliśmy je na ćwiczenie: poniżej dosłowne komunikaty javac z JDK 25 i dwa wyjątki z uruchomienia. Każdy przypadek odtworzyliśmy na osobnym pliku.
Źródło: komunikaty javac i JVM z OpenJDK 25.0.2 z opcją --release 25, odtworzone 11.10.2026.

Ćwiczenie z rozwiązaniem
Zadanie: napiszcie metodę czyPoprawnyEmail, która przyjmie adres z formularza i zwróci true albo false. Użyjcie wyłącznie metod z tej lekcji. Adres może mieć spacje na brzegach, ale musi zawierać dokładnie jedną małpę, przed nią co najmniej jeden znak, a po niej kropkę, która nie stoi na końcu. Zanim zajrzycie do rozwiązania, wypiszcie siedem przypadków testowych.
Walidator.java, rozwiązanie z wynikami
public class Walidator {
static boolean czyPoprawnyEmail(String email) {
if (email == null || email.isBlank()) {
return false;
}
String e = email.strip();
int malpa = e.indexOf('@');
return malpa > 0
&& malpa == e.lastIndexOf('@')
&& e.indexOf('.', malpa) > malpa + 1
&& !e.endsWith(".")
&& !e.contains(" ");
}
public static void main(String[] args) {
System.out.println(czyPoprawnyEmail("tester@qualityisland.pl")); // true
System.out.println(czyPoprawnyEmail(" tester@qualityisland.pl ")); // true
System.out.println(czyPoprawnyEmail("tester.qualityisland.pl")); // false
System.out.println(czyPoprawnyEmail("@qualityisland.pl")); // false
System.out.println(czyPoprawnyEmail("a@@b.pl")); // false
System.out.println(czyPoprawnyEmail("tester@pl.")); // false
System.out.println(czyPoprawnyEmail(null)); // false
}
}
Na co zwrócić uwagę. Najpierw obsługujemy null i pusty tekst, dopiero potem wołamy metody. Przypadki w main pokrywają adres poprawny, ze spacjami, bez małpy, bez nazwy użytkownika, z dwiema małpami, z kropką na końcu i null. W prawdziwym projekcie taką walidację zostawcie bibliotece, ale jako ćwiczenie z metod String sprawdza się świetnie. Przepiszcie te przypadki na testy JUnit z sekcji o asercjach, a dostaniecie kompletny zestaw do repozytorium.
Co dalej po tej lekcji
Powtórka
Instrukcja switch: porównywanie tekstów w case i ochrona przed null.
Następny krok
Tablice i split: jak przechowywać wiele Stringów i iterować po nich.
Na szkoleniu
Java dla testerów z trenerem, z przeglądem Waszego kodu i asercjami w prawdziwym projekcie.
Jeśli wolicie przećwiczyć Stringi, kolekcje i wyjątki pod okiem trenera, zobaczcie szkolenie programowanie w Javie dla testerów. Gdy zaczynacie od zera, lepszym wyborem będzie wprowadzenie do programowania od podstaw w Javie. Gotowi na przeglądarkę? Wtedy szkolenie z automatyzacji testów w Selenium albo Playwright w Javie. Przez szkolenia Quality Island przeszło już ponad 2 500 specjalistów. Z innymi testerami, którzy przechodzą tę samą drogę, spotkacie się na Testing Ground Conference, a o ofertach pracy dla testerów znających Javę przeczytacie na QA Board.
Co zabrać z tego artykułu
01String to obiekt, nie typ prosty, i jest niezmienny. Każda metoda zwraca nowy String, więc wynik przypiszcie do zmiennej.
02Treść porównujcie przez equals, nigdy przez ==. Wartość z zewnątrz stawiajcie w argumencie: "OK".equals(x).
03Do testów wartości brzegowych używajcie repeat, do czyszczenia tekstu z UI strip, a twardą spację zamieńcie ręcznie.
04Wielkość liter zmieniajcie z Locale.ROOT, a liczby formatujcie z jawną lokalizacją, inaczej test padnie na serwerze CI.
05W asercjach na tekstach AssertJ daje czytelniejszy komunikat porażki niż assertTrue(contains(...)).
Macie testy, które padają przez spacje, wielkość liter albo format liczb, i nikt nie wie dlaczego? Uporządkujemy asercje i framework automatyzacji.
Powiązane na blogu Quality Island
- Wprowadzenie do języka Java i spis lekcji kursu
- 11. Instrukcja switch w Javie
- 13. Tablice w Javie
- Asercja: co to jest i jak używać asercji w testach automatycznych
- Java dla testerów automatyzujących: plan nauki
Pełna lista źródeł
- Oracle, Java SE 25 API: klasa String, 2025. Niezmienność, metody, przykład z „TITLE” i Locale.ROOT, metody od Javy 11.
- Oracle, The Java Language Specification, Java SE 25 Edition, rozdział 3.10.5: String Literals, 2025.
- Oracle, Java SE 25 API: klasa Objects, 2025.
- Oracle, Java SE 25 API: klasa StringBuilder, 2025.
- Oracle, The Java Tutorials: Strings, stan na 11.10.2026.
- Oracle, Oracle Java SE Support Roadmap, stan na 11.10.2026. Java 25 jako najnowsza wersja LTS.
- OpenJDK, JEP 254: Compact Strings, 2017, Java 9.
- OpenJDK, JEP 280: Indify String Concatenation, 2017, Java 9.
- OpenJDK, JEP 378: Text Blocks, 2020, Java 15.
- JUnit, JUnit User Guide, wersja 6.1.3, 2026.
- AssertJ, AssertJ Core documentation, stan na 11.10.2026.
- Dane własne Quality Island, 2026: ponad 450 000 napisanych testów automatycznych, ponad 2 500 przeszkolonych specjalistów.



