Text Statistics & Readability — Java source
Count words, sentences, paragraphs, characters, lines, and reading time, plus Flesch Reading Ease and Flesch-Kincaid grade-level readability scores.
This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// text-stats — text statistics & readability: chars, words, sentences, syllables, Flesch. Language: Java (17+). Port of src/lib/textStats.ts — String.length() counts UTF-16 code units exactly like the TS reference (\s is ASCII-only in Java regex).
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Text statistics & readability scoring, ported from the canonical TS lib. */
public final class TextStatsTool {
/** Full analysis — readability fields stay null when words or sentences are too few. */
public record TextStats(int characters, int charactersNoSpaces, int words, int sentences, int paragraphs,
int lines, int syllables, int readingTimeMs, int speakingTimeMs,
Double fleschReadingEase, Double fleschKincaidGrade, String readabilityLabel) {}
static final Pattern WORD_RE = Pattern.compile("[A-Za-z0-9'’-]+"); // U+2019 keeps contractions whole
static final Pattern SENTENCE_RE = Pattern.compile("[.!?]+(\\s|$)");
static final Pattern PARA_SEP_RE = Pattern.compile("\\n{2,}");
/** Syllables in one word via the vowel-group heuristic. */
public static int countSyllables(String word) {
StringBuilder b = new StringBuilder();
for (char c : word.toLowerCase(Locale.ROOT).toCharArray())
if (c >= 'a' && c <= 'z') b.append(c);
String w = b.toString();
if (w.isEmpty()) return 0;
if (w.length() <= 3) return 1;
String s = w.replaceAll("(?:[^laeiouy]es|ed|[^laeiouy]e)$", ""); // silent trailing e ('~le' keeps its syllable)
s = s.replaceFirst("^y", "");
int groups = 0;
for (Matcher m = Pattern.compile("[aeiouy]+").matcher(s); m.find(); ) groups++;
return Math.max(1, groups);
}
public static TextStats analyze(String input) {
String text = input == null ? "" : input;
List<String> words = new ArrayList<>();
Matcher m = WORD_RE.matcher(text);
while (m.find()) words.add(m.group());
int sentences = words.isEmpty() ? 0
: Math.max(1, (int) SENTENCE_RE.matcher(text).results().count());
int paragraphs = text.strip().isEmpty() ? 0
: (int) Arrays.stream(PARA_SEP_RE.split(text)).map(String::strip).filter(p -> !p.isEmpty()).count();
int lines = text.isEmpty() ? 0 : text.split("\n", -1).length;
int syllables = 0;
for (String w : words) syllables += countSyllables(w);
int readingMs = (int) Math.round(words.size() / 200.0 * 60000); // 200 wpm
int speakingMs = (int) Math.round(words.size() / 130.0 * 60000); // 130 wpm
Double fre = null, fkg = null;
String label = null;
if (!words.isEmpty() && sentences > 0) {
double wps = (double) words.size() / sentences, spw = (double) syllables / words.size();
fre = Math.round((206.835 - 1.015 * wps - 84.6 * spw) * 10) / 10.0;
fkg = Math.round((0.39 * wps + 11.8 * spw - 15.59) * 10) / 10.0;
label = fre >= 80 ? "Very Easy" : fre >= 70 ? "Easy" : fre >= 60 ? "Standard"
: fre >= 50 ? "Fairly Hard" : fre >= 30 ? "Hard" : "Very Hard";
}
return new TextStats(text.length(), text.replaceAll("\\s", "").length(), words.size(), sentences,
paragraphs, lines, syllables, readingMs, speakingMs, fre, fkg, label);
}
public static void main(String[] args) {
for (String text : new String[] {
"The quick brown fox jumps over the lazy dog.",
"Hi.\n\nMy name is Inigo Montoya. You killed my father; prepare to die!"}) {
TextStats s = analyze(text);
String flesch = s.readabilityLabel() != null
? String.format(Locale.ROOT, "%.1f (%s)", s.fleschReadingEase(), s.readabilityLabel()) : "n/a";
String grade = s.fleschKincaidGrade() != null
? String.format(Locale.ROOT, "%.1f", s.fleschKincaidGrade()) : "n/a";
System.out.printf(Locale.ROOT,
"chars=%d nospace=%d words=%d sentences=%d paragraphs=%d lines=%d syllables=%d " +
"reading=%dms speaking=%dms flesch=%s grade=%s%n",
s.characters(), s.charactersNoSpaces(), s.words(), s.sentences(), s.paragraphs(),
s.lines(), s.syllables(), s.readingTimeMs(), s.speakingTimeMs(), flesch, grade);
}
}
}
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 →