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 -> 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 →