Skip to content

Case Converter — Java source

Convert text between UPPER, lower, Title, Sentence, camelCase, PascalCase, snake_case, kebab-case, CONSTANT_CASE and slug. Live, one-click copy.

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

// case-converter — pure, deterministic text-case transformations.
//
// Language: Java (17+, standard library only)
// Source:   CosmoDev polyglot showcase port of the Case Converter tool.
//           Canonical: src/lib/case.ts (the live TypeScript) plus this
//           tool's python.py / rust.rs reference ports.
// License:  display source — part of CosmoDev's polyglot tool pages.
//
// Split any input into word tokens and re-emit it in upper / lower / title /
// sentence / camel / pascal / snake / kebab / constant / slug case — same
// inputs produce the same outputs as the TypeScript original, including edge
// cases. Case mapping is Unicode-aware via Locale.ROOT (culture-invariant,
// no Turkish-i surprises), matching JavaScript's toUpperCase / toLowerCase.

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Pure, deterministic text-case transformations: split any input into word
 * tokens and re-emit it in upper / lower / title / sentence / camel / pascal /
 * snake / kebab / constant / slug case.
 *
 * <p>Ported from src/lib/case.ts (the canonical TypeScript that powers the
 * live Case Converter tool on dev.cosmolabs.org). Functionally equivalent —
 * same inputs produce the same outputs, including edge cases.
 */
final class CaseConverter {

    private CaseConverter() {}

    // --- Compiled patterns ----------------------------------------------------
    // Each mirrors a regex from the TypeScript reference. UNICODE_CHARACTER_CLASS
    // widens \s / \S to Unicode whitespace, matching the Python and Rust ports.

    // A camelCase seam: a lowercase letter or digit immediately followed by an
    // uppercase letter. We splice a space in here before splitting.
    private static final Pattern CAMEL_BOUNDARY = Pattern.compile("([a-z0-9])([A-Z])");

    // Word delimiters: any run of whitespace, underscore, hyphen, or dot.
    private static final Pattern SEPARATORS =
            Pattern.compile("[\\s_\\-.]+", Pattern.UNICODE_CHARACTER_CLASS);

    // Anything that is not an ASCII letter or digit — stripped from each token.
    private static final Pattern NON_ALNUM = Pattern.compile("[^a-zA-Z0-9]");

    // A title "word": a maximal run that starts with an ASCII word character
    // (spelled out [A-Za-z0-9_] because Java's \w is Unicode-wide, unlike
    // JavaScript's) and continues with non-whitespace.
    private static final Pattern TITLE_WORD =
            Pattern.compile("[A-Za-z0-9_]\\S*", Pattern.UNICODE_CHARACTER_CLASS);

    /**
     * Tokenize {@code s} into word units.
     *
     * <p>The pipeline reproduces the TypeScript reference step for step:
     * <ol>
     *   <li>Insert a space at every camelCase boundary.
     *   <li>Split on runs of whitespace / underscore / hyphen / dot.
     *   <li>Strip remaining non-alphanumerics and drop empty tokens.
     * </ol>
     */
    public static List<String> words(String s) {
        String spaced = CAMEL_BOUNDARY.matcher(s).replaceAll("$1 $2");
        List<String> tokens = new ArrayList<>();
        for (String part : SEPARATORS.split(spaced)) {
            String cleaned = NON_ALNUM.matcher(part).replaceAll("");
            if (!cleaned.isEmpty()) { // filter(Boolean) in the TS source
                tokens.add(cleaned);
            }
        }
        return tokens;
    }

    /** Capitalize the first character and lowercase the rest. */
    public static String cap(String s) {
        if (s.isEmpty()) return s;
        return Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase(Locale.ROOT);
    }

    /** Upper-case every character. */
    public static String upper(String s) {
        return s.toUpperCase(Locale.ROOT);
    }

    /** Lower-case every character. */
    public static String lower(String s) {
        return s.toLowerCase(Locale.ROOT);
    }

    /**
     * Title case: capitalize the first character of each maximal word-run.
     * Characters between runs (spacing, punctuation) are left untouched.
     */
    public static String title(String s) {
        Matcher m = TITLE_WORD.matcher(s);
        StringBuilder out = new StringBuilder();
        while (m.find()) {
            m.appendReplacement(out, Matcher.quoteReplacement(cap(m.group())));
        }
        m.appendTail(out);
        return out.toString();
    }

    /**
     * Sentence case: lowercase + trim the whole string, then uppercase only
     * the first character. Empty input yields empty output.
     */
    public static String sentence(String s) {
        String t = s.toLowerCase(Locale.ROOT).strip();
        if (t.isEmpty()) return t;
        return Character.toUpperCase(t.charAt(0)) + t.substring(1);
    }

    /** camelCase: the first word stays lowercase, every later word is capitalized. */
    public static String camel(String s) {
        StringBuilder out = new StringBuilder();
        List<String> ws = words(s);
        for (int i = 0; i < ws.size(); i++) {
            out.append(i == 0 ? ws.get(i).toLowerCase(Locale.ROOT) : cap(ws.get(i)));
        }
        return out.toString();
    }

    /** PascalCase: every word capitalized and concatenated. */
    public static String pascal(String s) {
        StringBuilder out = new StringBuilder();
        for (String w : words(s)) {
            out.append(cap(w));
        }
        return out.toString();
    }

    /** snake_case — "_".join(words).lower() in the reference. */
    public static String snake(String s) {
        return String.join("_", words(s)).toLowerCase(Locale.ROOT);
    }

    /** kebab-case — "-".join(words).lower() in the reference. */
    public static String kebab(String s) {
        return String.join("-", words(s)).toLowerCase(Locale.ROOT);
    }

    /** CONSTANT_CASE — "_".join(words).upper() in the reference. */
    public static String constant(String s) {
        return String.join("_", words(s)).toUpperCase(Locale.ROOT);
    }

    /** slug (kebab's URL-safe twin) — "-".join(words).lower() in the reference. */
    public static String slug(String s) {
        return String.join("-", words(s)).toLowerCase(Locale.ROOT);
    }

    /**
     * The case-transform registry: CaseId -&gt; pure function. Keys mirror the
     * TypeScript {@code caseTransforms} object 1:1 (LinkedHashMap preserves
     * insertion order); the map is immutable.
     */
    public static final Map<String, UnaryOperator<String>> CASE_TRANSFORMS = buildTransforms();

    private static Map<String, UnaryOperator<String>> buildTransforms() {
        Map<String, UnaryOperator<String>> m = new LinkedHashMap<>();
        m.put("upper", CaseConverter::upper);
        m.put("lower", CaseConverter::lower);
        m.put("title", CaseConverter::title);
        m.put("sentence", CaseConverter::sentence);
        m.put("camel", CaseConverter::camel);
        m.put("pascal", CaseConverter::pascal);
        m.put("snake", CaseConverter::snake);
        m.put("kebab", CaseConverter::kebab);
        m.put("constant", CaseConverter::constant);
        m.put("slug", CaseConverter::slug);
        return Collections.unmodifiableMap(m);
    }

    /** Valid case identifiers, in registry order. */
    public static List<String> caseIds() {
        return List.copyOf(CASE_TRANSFORMS.keySet());
    }

    /**
     * Apply a named case transform.
     *
     * @throws IllegalArgumentException if {@code caseId} is not a known case
     */
    public static String convert(String caseId, String s) {
        UnaryOperator<String> fn = CASE_TRANSFORMS.get(caseId);
        if (fn == null) throw new IllegalArgumentException("Unknown case: " + caseId);
        return fn.apply(s);
    }

    /** Like {@link #convert} but returns {@link Optional#empty()} for an unknown case id. */
    public static Optional<String> tryConvert(String caseId, String s) {
        UnaryOperator<String> fn = CASE_TRANSFORMS.get(caseId);
        return fn == null ? Optional.empty() : Optional.of(fn.apply(s));
    }
}

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 →