Budowanie adresów URL i kodowanie parametrów
- WWW i API
- poziom: początkujący
- aktualizacja: 16 sierpnia 2026
Ręczne sklejanie adresu operatorem + działa do pierwszej spacji, polskiej litery albo znaku & w szukanej frazie — potem serwer dostaje adres, którego nie da się rozebrać z powrotem. Ten skrypt pokazuje, jak to zrobić poprawnie: klucz i wartość kodowane osobno, a przede wszystkim różnicę, o którą potyka się większość osób. URLEncoder zamienia spację na + i w query stringu jest to prawidłowe, ale w ścieżce adresu + znaczy dosłownie plus — tam spacja musi być zapisana jako %20.
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Budowanie adresów URL i kodowanie parametrów — skryptyJAVA.pl
* Zapisz jako: AdresyUrl.java
*/
public class AdresyUrl {
/**
* Skleja adres bazowy z parametrami zapytania. Klucz i wartość kodowane są osobno —
* dopiero wtedy znaki "=" oraz "&" wstawione przez nas pozostają separatorami,
* a te same znaki wpisane przez użytkownika zamieniają się w %3D i %26.
*
* @param bazowy adres bez znaku zapytania albo z gotowym początkiem query;
* adres z kotwicą "#" się nie nada — parametry wylądowałyby za nią
* @param parametry pary klucz-wartość; wpis o wartości null jest pomijany
*/
public static String zbudujAdres(String bazowy, Map<String, String> parametry) {
StringBuilder adres = new StringBuilder(bazowy);
char separator = bazowy.contains("?") ? '&' : '?';
for (Map.Entry<String, String> wpis : parametry.entrySet()) {
if (wpis.getValue() == null) { continue; } // null to nie to samo co pusty napis
adres.append(separator)
.append(kodujParametr(wpis.getKey()))
.append('=')
.append(kodujParametr(wpis.getValue()));
separator = '&';
}
return adres.toString();
}
/** Kodowanie do query stringu: spacja zamienia się na "+", dokładnie jak w formularzu HTML. */
public static String kodujParametr(String tekst) {
return URLEncoder.encode(tekst, StandardCharsets.UTF_8);
}
/**
* Kodowanie POJEDYNCZEGO segmentu ścieżki. W ścieżce "+" znaczy dosłownie plus,
* więc spacja musi być zapisana jako %20. Podmiana jest bezpieczna: prawdziwy plus
* z tekstu został wcześniej zamieniony przez URLEncoder na %2B.
*/
public static String kodujSegmentSciezki(String segment) {
return URLEncoder.encode(segment, StandardCharsets.UTF_8).replace("+", "%20");
}
/** Skleja ścieżkę z osobnych segmentów; ukośniki wstawiamy my, więc nie znikną w kodowaniu. */
public static String zbudujSciezke(String... segmenty) {
StringBuilder sciezka = new StringBuilder();
for (String segment : segmenty) {
sciezka.append('/').append(kodujSegmentSciezki(segment));
}
return sciezka.toString();
}
/**
* Rozbija surowy query string na mapę. Parametr bez znaku "=" dostaje pusty napis,
* tak samo jak parametr z pustą wartością — w adresie te dwa zapisy wyglądają inaczej,
* ale w praktyce znaczą to samo. LinkedHashMap zachowuje kolejność z adresu.
* Powtórzony klucz (tag=a&tag=b) nadpisuje poprzedni — w mapie zostaje ostatnia wartość.
*/
public static Map<String, String> rozbijQuery(String query) {
Map<String, String> parametry = new LinkedHashMap<>();
if (query == null || query.isEmpty()) { return parametry; }
String tresc = query.startsWith("?") ? query.substring(1) : query;
for (String para : tresc.split("&")) {
if (para.isEmpty()) { continue; }
int rownosc = para.indexOf('=');
String klucz = rownosc < 0 ? para : para.substring(0, rownosc);
String wartosc = rownosc < 0 ? "" : para.substring(rownosc + 1);
parametry.put(URLDecoder.decode(klucz, StandardCharsets.UTF_8),
URLDecoder.decode(wartosc, StandardCharsets.UTF_8));
}
return parametry;
}
/** Rozkłada adres na części przez java.net.URI; getRaw... oddaje wersję jeszcze zakodowaną. */
public static String opiszAdres(String adres) {
URI uri = URI.create(adres);
int port = uri.getPort(); // -1 oznacza brak portu w adresie, czyli 80 albo 443
return "schemat=" + uri.getScheme()
+ " | host=" + uri.getHost()
+ " | port=" + (port == -1 ? "domyślny" : String.valueOf(port))
+ " | ścieżka=" + uri.getRawPath()
+ " | query=" + uri.getRawQuery();
}
/** Dokleja adres względny, ale odrzuca zmianę schematu lub serwera bazowego. */
public static String doklejSciezke(String bazowy, String wzgledny) {
URI baza = URI.create(bazowy);
URI wynik = baza.resolve(wzgledny);
if (!baza.isAbsolute() || baza.getRawAuthority() == null
|| !baza.getScheme().equalsIgnoreCase(wynik.getScheme())
|| !baza.getRawAuthority().equalsIgnoreCase(wynik.getRawAuthority())) {
throw new IllegalArgumentException("Adres względny prowadzi poza serwer bazowy.");
}
return wynik.toString();
}
// ── PRZYKŁADY ───────────────────────────────────────────
public static void main(String[] args) {
Map<String, String> parametry = new LinkedHashMap<>();
parametry.put("fraza", "żółw ćma");
parametry.put("miasto", "Łódź");
parametry.put("sortuj", "cena rosnąco");
parametry.put("strona", "2");
String adres = zbudujAdres("https://przyklad.pl/szukaj", parametry);
System.out.println("adres: " + adres);
System.out.println("dopisany: " + zbudujAdres(adres, Map.of("format", "json")));
// Ten sam tekst w ścieżce wygląda inaczej: %20 zamiast "+"
System.out.println("parametr: " + kodujParametr("raport marzec 2026.pdf"));
System.out.println("segment: " + kodujSegmentSciezki("raport marzec 2026.pdf"));
System.out.println("ścieżka: " + zbudujSciezke("pliki", "Łódź", "raport marzec.pdf"));
// Do rozbierania bierzemy getRawQuery(), czyli wersję jeszcze nie zdekodowaną
String query = URI.create(adres).getRawQuery() + "&debug&pusty=";
System.out.println("parametry z adresu:");
for (Map.Entry<String, String> wpis : rozbijQuery(query).entrySet()) {
System.out.println(" " + wpis.getKey() + " -> [" + wpis.getValue() + "]");
}
System.out.println(opiszAdres("https://api.przyklad.pl:8443/v1/klienci?limit=20&od=2026-01-01"));
System.out.println(opiszAdres("https://przyklad.pl/szukaj"));
String katalog = "https://przyklad.pl/dokumenty/2026/";
System.out.println("względny: " + doklejSciezke(katalog, "raport.pdf"));
System.out.println("od korzenia: " + doklejSciezke(katalog, "/cennik.pdf"));
System.out.println("poziom wyżej: " + doklejSciezke(katalog, "../2025/raport.pdf"));
}
}
Jak użyć tego skryptu
- Wklej kod do pliku
AdresyUrl.java— nazwa musi być identyczna jak nazwa klasy publicznej — i uruchom poleceniemjava AdresyUrl.java. Wydruk pokaże gotowy adres z frazą „żółw ćma” i miastem „Łódź”, ten sam tekst zakodowany raz dla parametru, a raz dla ścieżki, oraz komplet parametrów odczytanych z powrotem. - We własnym projekcie wywołuj
AdresyUrl.zbudujAdres(bazowy, parametry), przekazującLinkedHashMap<String, String>— zwykłaHashMapustawi parametry w przypadkowej kolejności, przez co ten sam zestaw filtrów da za każdym razem inny adres i rozjedzie Ci się cache. Wpis o wartościnulljest pomijany, więc opcjonalne filtry możesz wrzucać do mapy bez sprawdzania warunkiem. - Do ścieżek używaj
zbudujSciezke("pliki", "Łódź", "raport marzec.pdf")i podawaj każdy segment osobno. Nie przepuszczaj przez tę metodę gotowego kawałka ścieżki z ukośnikami —URLEncoderzamieni każdy ukośnik na%2Fi zamiast trzech katalogów dostaniesz jedną dziwną nazwę pliku. - Parametry z gotowego adresu odczytasz przez
rozbijQuery(URI.create(adres).getRawQuery()). Sięgaj pogetRawQuery(), a nie pogetQuery()— ta druga metoda oddaje tekst już zdekodowany, więc wartość zawierająca&albo=rozpadnie się przy dzieleniu na osobne, fałszywe parametry. Adresy względne z odpowiedzi API doklejaj metodądoklejSciezke(...): poURI.resolvesprawdza ona schemat i pełne authority, więc odrzuci także zapis//obcy-serwer.pl/plik.
Warto wiedzieć
- Najczęstszy błąd:
URLEncoderkoduje spację jako+. W query stringu to poprawne, bo obowiązuje tam formatapplication/x-www-form-urlencoded, ale w ścieżce adresu+jest zwykłym znakiem plus i serwer poszuka pliku o nazwieraport+marzec.pdf. DlategokodujSegmentSciezki(...)podmienia+na%20— bezpiecznie, bo prawdziwy plus z tekstu został już wcześniej zamieniony na%2B. - Nie przepuszczaj przez
URLEncodercałego adresu. Ta klasa koduje wszystko, czego nie zna, więc zhttps://przyklad.pl/a bzrobihttps%3A%2F%2Fprzyklad.pl%2Fa+b— razem z://i ukośnikami. Koduj wyłącznie pojedyncze wartości, a szkielet adresu składaj sam. - Query string wycieka nagłówkiem
Referer: gdy z Twojej strony użytkownik kliknie link na obcą witrynę, tamten serwer dostanie w prezencie komplet parametrów adresu, na którym stał. Dlatego dozbudujAdres(...)nie wkładaj haseł, tokenów resetu hasła ani kluczy API — nawet jednorazowych. Takie dane przekazuje się nagłówkiem żądania albo w ciele metody POST. rozbijQuery(...)opiera się naURLDecoder, a ten przy uszkodzonym zapisie procentowym rzucaIllegalArgumentException— wywróci go zarówno?rabat=100%(urwany znak procenta), jak i?a=%ZZ. To wyjątek niesprawdzany, więc kompilator o nim nie przypomni, a query string prawie zawsze pochodzi od użytkownika. Jeśli podajesz tej metodzie cudzy adres, otocz wywołanie blokiemtryi odpowiadaj kodem 400 zamiast pozwolić, żeby wyjątek ubił żądanie.
Podobne skrypty
Pobieranie danych z API przez HttpClient
Klient HTTP na wbudowanym HttpClient z Javy 11: żądania GET i POST z ciałem JSON, limity czasu, sprawdzanie kodu odpowiedzi, pobieranie pliku prosto na dysk i wersja asynchroniczna — bez żadnych bibliotek.
średni Zobacz kod → WWW i APIProsty serwer HTTP bez frameworka
Mały serwer WWW na wbudowanej klasie HttpServer: routing, parametry zapytania, odpowiedzi UTF-8 oraz poprawne kody HTTP z obsługą HEAD i 405. Przykład sam uruchamia serwer lokalny, odpytuje go i zatrzymuje, więc nic nie zostaje w tle.
średni Zobacz kod → WWW i APIPobieranie pliku z paskiem postępu
Strumieniowe pobieranie dużego pliku przez HttpClient z procentowym postępem, limitem czasu na połączenie i bezpiecznym plikiem tymczasowym. Samodzielny przykład uruchamia lokalny serwer, więc działa bez internetu.
średni Zobacz kod →