Skip to content

UTM Link Builder — Java source

Build campaign tracking URLs with utm_source, utm_medium and utm_campaign parameters. Bulk mode processes a whole list, presets and import round-trip existing tracking URLs, and a validator flags attribution-breaking values — runs entirely in your browser.

This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.

// utm-link-builder — campaign URL builder with canonical utm_* ordering. Java (17+, JDK only). Port of src/lib/utm.ts build(); lint/bulk/preset helpers live in the TypeScript/Go sources. Kept query pairs pass through verbatim; new utm values are form-encoded.

import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

public class UtmLinkBuilder {
    private static final List<Map.Entry<String, String>> CANONICAL = List.of(
            Map.entry("source", "utm_source"), Map.entry("medium", "utm_medium"),
            Map.entry("campaign", "utm_campaign"), Map.entry("term", "utm_term"),
            Map.entry("content", "utm_content"));

    static String build(String baseUrl, Map<String, String> params) {
        try {
            String text = baseUrl.contains("://") ? baseUrl : "https://" + baseUrl;
            URI uri = URI.create(text);
            if (uri.getHost() == null) return null; // unparseable / no host

            StringBuilder query = new StringBuilder();
            if (uri.getRawQuery() != null) { // keep unrelated pairs, drop stale utm_*
                for (String pair : uri.getRawQuery().split("&")) {
                    String key = pair.substring(0, Math.max(pair.indexOf('='), 0));
                    if (!key.isEmpty() && !key.startsWith("utm_")) {
                        if (query.length() > 0) query.append('&');
                        query.append(pair);
                    }
                }
            }
            for (Map.Entry<String, String> pair : CANONICAL) {
                String value = params.get(pair.getKey());
                if (value != null && !value.isEmpty()) {
                    if (query.length() > 0) query.append('&');
                    query.append(pair.getValue()).append('=')
                         .append(URLEncoder.encode(value, StandardCharsets.UTF_8));
                }
            }

            StringBuilder out = new StringBuilder(uri.getScheme()).append("://").append(uri.getHost());
            if (uri.getPort() != -1) out.append(':').append(uri.getPort());
            out.append(uri.getRawPath() == null ? "" : uri.getRawPath());
            if (query.length() > 0) out.append('?').append(query);
            if (uri.getRawFragment() != null) out.append('#').append(uri.getRawFragment());
            return out.toString();
        } catch (IllegalArgumentException e) {
            return null;
        }
    }
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →