You’ve probably done this before: you need to generate a separator line, build a predictable identifier, or render visual markers in output, and you reach for a loop out of habit. I did that for years. It worked, but it also added noise to code that should have been simple. Java’s String.repeat(int count) changed that workflow for me because it expresses intent directly: repeat this text N times.
When I review production code, I look for small readability wins that reduce future bugs. repeat() is one of those wins. It is short, clear, and surprisingly rich once you understand its edge behavior, performance profile, and when not to use it.
In this deep dive, I’ll walk through how repeat() behaves, what happens with zero, empty, negative, and very large counts, and how I apply it in real systems. I’ll also share runnable Java examples, performance guidance, testing patterns, and practical rules to decide between repeat(), loops, and StringBuilder. If you write Java regularly, this is one of those methods worth putting in your default toolbox.
Why repeat() matters in day-to-day Java code
repeat() was added in Java 11, and by 2026 it is present in nearly every active Java backend I work on. The method looks small, but it solves a common readability problem: repetitive string construction used to require control-flow code for what is fundamentally a data operation.
Before repeat(), I often saw this pattern:
- create
StringBuilder - write a loop with a counter
- append the same token each iteration
- convert to
String
That approach is still valid in some situations, but for fixed repetition it hides intent behind mechanics. With repeat(), you say exactly what you mean:
"-".repeat(80)for a divider line"0".repeat(6)for fixed-width zero prefixes" ".repeat(indentLevel * 2)for indentation
When I read code six months later, this clarity pays off immediately. I no longer parse loop logic just to confirm that it repeats a constant token.
I treat repeat() as first choice when all of these are true:
- I repeat one immutable base string.
- The repeat count is known at execution time.
- I need the final string value, not streamed output.
In those conditions, repeat() gives cleaner code and behavior that is easy to reason about and test.
Method contract: syntax, return value, and exact behavior
The signature is straightforward:
String result = source.repeat(count);
- Parameter:
count(int) is the number of repetitions. - Return type:
String. - Original string is never mutated because strings are immutable.
I rely on five behavioral rules:
- If
count == 0, the result is"". - If
source.isEmpty(), the result is""for any non-negativecount. - If
count < 0, Java throwsIllegalArgumentException. - If result length would exceed implementation limits or memory availability, runtime failure can occur (typically
OutOfMemoryErrorin practice for huge allocations). repeat()is deterministic and side-effect free, so it is naturally thread-safe.
Minimal runnable example:
public class RepeatBasicDemo {
public static void main(String[] args) {
String segment = "abc";
int count = 3;
String result = segment.repeat(count);
System.out.println(result);
}
}
Expected output:
abcabcabc
Happy path is simple. The real value comes from understanding boundary behavior.
Edge cases you should handle intentionally
I see most bugs around repeat() in boundary handling, not syntax.
1) count = 0 returns an empty string
public class RepeatZeroCountDemo {
public static void main(String[] args) {
String token = "xyz";
String result = token.repeat(0);
System.out.println("Length: " + result.length());
System.out.println("Value: [" + result + "]");
}
}
Output:
Length: 0
Value: []
I often print brackets in debug logs to prove I got an empty string, not null.
2) Empty source string always stays empty
public class RepeatEmptySourceDemo {
public static void main(String[] args) {
String source = "";
System.out.println(source.repeat(1).length());
System.out.println(source.repeat(50).length());
}
}
Both lines print 0.
3) Negative count throws IllegalArgumentException
public class RepeatNegativeCountDemo {
public static void main(String[] args) {
try {
System.out.println("#".repeat(-2));
} catch (IllegalArgumentException ex) {
System.out.println("Invalid repeat count");
System.out.println(ex.getMessage());
}
}
}
I validate at API boundaries so domain logic can assume non-negative counts.
4) Very large counts can pressure memory
repeat() must allocate the full result. If source.length() * count is huge, memory pressure appears quickly.
public class RepeatDefensiveGuardDemo {
private static final int MAXRESULTLENGTH = 1000000;
static String safeRepeat(String source, int count) {
if (count < 0) {
throw new IllegalArgumentException("count must be >= 0");
}
long projectedLength = (long) source.length() * count;
if (projectedLength > MAXRESULTLENGTH) {
throw new IllegalArgumentException(
"Result too large: " + projectedLength + " characters"
);
}
return source.repeat(count);
}
public static void main(String[] args) {
System.out.println(safeRepeat("ab", 3));
}
}
I strongly recommend hard caps for externally supplied counts.
5) Unicode is safe, rendering may still vary
public class RepeatUnicodeDemo {
public static void main(String[] args) {
String emoji = "🙂";
System.out.println(emoji.repeat(4));
String accentForm = "e\u0301"; // e + combining accent
System.out.println(accentForm.repeat(3));
}
}
Java repeats Unicode data correctly. The caveat is display: fonts and terminals can render combined characters differently.
Practical examples I actually use in real projects
Here are patterns I repeatedly recommend in code reviews.
Example 1: Indentation for generated text
public class RepeatIndentDemo {
static void printNode(String nodeName, int depth) {
String indent = " ".repeat(depth * 2);
System.out.println(indent + "- " + nodeName);
}
public static void main(String[] args) {
printNode("root", 0);
printNode("service", 1);
printNode("endpoint", 2);
}
}
The intent is obvious: two spaces per depth level.
Example 2: Fixed-width left padding
public class RepeatPaddingDemo {
static String leftPadWithZeros(String numberText, int width) {
if (numberText.length() >= width) return numberText;
int zerosNeeded = width – numberText.length();
return "0".repeat(zerosNeeded) + numberText;
}
public static void main(String[] args) {
System.out.println(leftPadWithZeros("42", 6));
System.out.println(leftPadWithZeros("2026", 6));
}
}
Output:
000042
002026
Example 3: Log separators for incident readability
public class RepeatLogSeparatorDemo {
static void logSection(String title) {
String line = "=".repeat(40);
System.out.println(line);
System.out.println(title);
System.out.println(line);
}
public static void main(String[] args) {
logSection("PAYMENT RECONCILIATION RUN");
}
}
Example 4: SQL placeholder generation
public class RepeatSqlPlaceholderDemo {
static String placeholders(int count) {
if (count <= 0) {
throw new IllegalArgumentException("count must be > 0");
}
String raw = "?,".repeat(count);
return raw.substring(0, raw.length() – 1);
}
public static void main(String[] args) {
System.out.println(placeholders(5));
}
}
Output:
?,?,?,?,?
Example 5: CLI progress bar rendering
public class RepeatProgressBarDemo {
static String renderProgressBar(int totalSlots, int completedSlots) {
if (totalSlots < 0 |
completedSlots > totalSlots) {
throw new IllegalArgumentException("Invalid progress inputs");
}
String done = "#".repeat(completedSlots);
String pending = "-".repeat(totalSlots – completedSlots);
return "[" + done + pending + "]";
}
public static void main(String[] args) {
System.out.println(renderProgressBar(20, 7));
}
}
Output:
[#######————-]Example 6: Masking sensitive values in logs
public class RepeatMaskingDemo {
static String maskExceptLast4(String input) {
if (input == null || input.length() <= 4) return input;
int maskLength = input.length() – 4;
return "*".repeat(maskLength) + input.substring(maskLength);
}
public static void main(String[] args) {
System.out.println(maskExceptLast4("1234567812345678"));
}
}
Output:
5678
Example 7: Text table rendering
public class RepeatTableDemo {
static void printRow(String left, String right, int width) {
String content = left + " | " + right;
int remaining = Math.max(0, width – content.length());
System.out.println(content + " ".repeat(remaining));
}
public static void main(String[] args) {
int width = 30;
System.out.println("-".repeat(width));
printRow("Service", "READY", width);
printRow("Worker", "DEGRADED", width);
System.out.println("-".repeat(width));
}
}
Example 8: Lightweight ASCII banners for scripts
public class RepeatBannerDemo {
static void banner(String text) {
String border = "*".repeat(text.length() + 4);
System.out.println(border);
System.out.println(" " + text + " ");
System.out.println(border);
}
public static void main(String[] args) {
banner("Release completed");
}
}
These are not toy examples. I use variants of them in CLI tools, backend diagnostics, batch jobs, and quick operational scripts.
Performance and memory: what I watch in production
repeat() is generally efficient for fixed repetition because it allocates output once and fills it through optimized internal routines. For typical backend workloads, this is fast enough that the method call is rarely your bottleneck.
Where I see issues:
- multi-megabyte output strings created frequently
- nested loops where
repeat()gets called millions of times - building giant temporary strings that are immediately discarded
Practical rules I use:
- For a single final repeated fragment, prefer
repeat(). - For iterative, mixed-content assembly, prefer
StringBuilder. - Cap externally controlled counts.
- Avoid allocating repeated fragments inside hot loops when you can cache.
Caching pattern:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class RepeatCacheDemo {
private static final Map INDENT_CACHE = new ConcurrentHashMap();
static String indent(int depth) {
return INDENT_CACHE.computeIfAbsent(depth, d -> " ".repeat(d * 2));
}
public static void main(String[] args) {
System.out.println("[" + indent(4) + "node]");
System.out.println("[" + indent(4) + "child]");
}
}
Benchmarking guidance (without overfitting)
If performance matters, I benchmark with JMH rather than eyeballing local runs. In many environments:
- small repeats (count under ~100) are effectively negligible in request latency
- moderate repeats (hundreds to low thousands) are still cheap unless done at massive scale
- very large repeats are dominated by allocation and GC behavior
I focus on allocation rate and GC pause patterns, not just raw method throughput.
repeat() vs older approaches: decision table
Here is the decision framework I use in modern Java codebases.
Best use case
Allocation control
—
—
String.repeat(count) Fixed repetition of one token
Good
StringBuilder loop Dynamic content per iteration
Excellent
String.join("", Collections.nCopies(...)) Legacy compatibility style
Extra collection overhead
for + += Tiny throwaway snippets
Poor in loops
Concrete rule: if your intent is literally “repeat this token N times,” choose repeat() first.
When I do NOT use repeat()
This is just as important as knowing when to use it.
I avoid repeat() when:
- Output is streamed directly to an
OutputStreamorWriterand I do not need the full string in memory. - Each iteration changes content (for example, adding an index or conditional token).
- I need lazy generation of a large sequence.
- I am formatting large datasets and want chunked writes to reduce peak memory.
Streaming example with Writer:
import java.io.IOException;
import java.io.StringWriter;
public class StreamInsteadOfRepeatDemo {
public static void main(String[] args) throws IOException {
StringWriter writer = new StringWriter();
for (int i = 0; i < 1_000; i++) {
writer.write(‘-‘);
}
System.out.println(writer.toString().length());
}
}
This can be better if you were going to write directly to a stream anyway.
Common mistakes and how I avoid them
Mistake 1: No validation of external counts
If count comes from query params, config, or payloads, negative and absurdly large values are common.
Fix: validate at boundaries and return domain-specific errors.
Mistake 2: Treating empty result as failure
repeat(0) returning "" is valid behavior, not an error.
Fix: encode business rules explicitly; if zero is forbidden, reject zero early.
Mistake 3: Integer math assumptions
source.length() * count can overflow if both are int and very large.
Fix: use long projected = (long) source.length() * count; before comparisons.
Mistake 4: Calling repeat() in hot nested loops
Even good APIs can create too many temporary objects.
Fix: hoist repeated fragments, cache common values, or use StringBuilder outside the inner loop.
Mistake 5: Weak boundary testing
Teams often test only count = 3 and miss real production cases.
Fix: test -1, 0, 1, typical value, and high boundary.
JUnit 5 test suite I recommend
I use tests like this as a baseline contract suite.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class RepeatMethodTest {
@Test
void repeatsNormally() {
assertEquals("ababab", "ab".repeat(3));
}
@Test
void zeroCountReturnsEmpty() {
assertEquals("", "ab".repeat(0));
}
@Test
void emptySourceStaysEmpty() {
assertEquals("", "".repeat(100));
}
@Test
void negativeCountThrows() {
assertThrows(IllegalArgumentException.class, () -> "ab".repeat(-1));
}
@Test
void unicodeRepeatIsStable() {
assertEquals("🙂🙂🙂", "🙂".repeat(3));
}
@Test
void projectedLengthGuardWorks() {
String source = "abcd";
int count = 250_000;
long projected = (long) source.length() * count;
assertTrue(projected > 500_000);
}
}
If I expose count in an API, I add service-level tests that verify error responses for invalid ranges.
Production-grade helper patterns
I rarely call repeat() directly in every business method. I prefer tiny utility wrappers for consistency.
Pattern 1: Guarded utility
public final class Strings {
private Strings() {}
public static String repeatSafe(String source, int count, int maxLen) {
if (source == null) {
throw new IllegalArgumentException("source must not be null");
}
if (count < 0) {
throw new IllegalArgumentException("count must be >= 0");
}
long projected = (long) source.length() * count;
if (projected > maxLen) {
throw new IllegalArgumentException("result too large: " + projected);
}
return source.repeat(count);
}
}
Pattern 2: Domain-specific wrappers
Instead of calling " ".repeat(level * 2) all over code, I create indent(level).
Benefits:
- centralized rules
- easier profiling and caching
- clearer call sites
Pattern 3: Input normalization
If user inputs include whitespace or malformed numeric values, normalize first, then repeat.
This avoids repeating hidden invalid tokens and keeps failures explicit.
Unicode and text correctness: deeper notes
Most developers think repeat() problems are about encoding, but in Java the bigger risk is semantic text handling.
What I keep in mind:
length()counts UTF-16 code units, not user-perceived grapheme clusters.- Repeating a combining sequence is valid but may render differently across fonts.
- If UI alignment matters (CLI columns, PDF, HTML tables), visual width may differ from string length.
Practical guidance:
- For backend identifiers or machine text,
repeat()is straightforward. - For user-visible aligned text with emojis/CJK/combining marks, validate rendering in target environment.
- If you need display width, use a library designed for grapheme/width handling rather than naive
length()assumptions.
Security and reliability considerations
repeat() itself is safe, but untrusted counts can become a denial-of-service vector through memory allocation pressure.
I defend against this by:
- hard limits in request validation
- conservative defaults in configs
- request-level timeouts for expensive formatting paths
- metrics for rejected oversize requests
Example API validation logic:
static int normalizeCount(int raw) {
int max = 10_000;
if (raw max) {
throw new IllegalArgumentException("count must be between 0 and " + max);
}
return raw;
}
If an endpoint is publicly exposed, I treat count as potentially hostile input.
Migration guide: replacing old patterns safely
I often modernize legacy code from Java 8 style loops to Java 11+ repeat().
Before
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append("-");
}
String line = sb.toString();
After
String line = "-".repeat(n);
Migration checklist I use:
- Confirm runtime is Java 11+ everywhere (local, CI, production).
- Preserve validation behavior for invalid counts.
- Keep existing output tests.
- Benchmark only if path is performance-critical.
- Prefer small, focused refactors over massive mechanical edits.
This upgrade usually reduces code size and improves readability immediately.
repeat() with formatting APIs
repeat() works well alongside formatters when responsibilities are clear.
Examples:
repeat()for structural text (-----, indentation)String.formatorFormatterfor numeric/date formattingDecimalFormatfor locale-aware numbers
Hybrid example:
public class RepeatAndFormatDemo {
public static void main(String[] args) {
String title = "Daily Summary";
String line = "-".repeat(30);
int count = 42;
System.out.println(line);
System.out.println(title);
System.out.println(String.format("Processed: %06d", count));
System.out.println(line);
}
}
I avoid using repeat() to reimplement formatting features already handled better by dedicated APIs.
Observability tips for repeat-heavy paths
If you suspect string generation is expensive, I instrument first.
What I log or measure:
- count distributions (p50/p95/p99)
- projected output lengths
- rejection counts from safety caps
- allocation rate in profiler snapshots
This helps me distinguish real hotspots from cosmetic concerns.
Quick reference: practical rules I follow
- Use
repeat()when repeating one fixed token into one final string. - Validate
countat boundaries (count >= 0, plus max cap). - Use
longfor projected length checks. - Prefer
StringBuilderwhen each iteration adds different content. - Cache common repeated fragments in hot code paths.
- For user-visible Unicode alignment, test actual rendering.
FAQ
Is repeat() available in Java 8?
No. It is available from Java 11 onward.
Does repeat() modify the original string?
No. Strings are immutable; it returns a new value.
What happens for count = 0?
You get "".
What exception is thrown for negative count?
IllegalArgumentException.
Is repeat() faster than loops?
Often yes for fixed repetition, but I still profile critical paths. The biggest gains are readability and correctness, not micro-optimization bragging rights.
Should I always replace loops with repeat()?
Not always. If your content changes per iteration or you stream output, loops or builders are often better.
Final takeaway
String.repeat(int count) is one of those APIs that looks tiny but has outsized impact on code quality. I use it because it makes intent explicit, reduces boilerplate, and standardizes a very common operation. The key is using it deliberately: understand edge behavior, validate external counts, and respect memory limits for large outputs.
If you adopt one simple rule, make it this: when your intent is literally repeat this exact token N times, use repeat() first, then switch to StringBuilder only when the output logic becomes dynamic. That single habit improves readability across utility code, services, logging, CLI tools, and test fixtures.



