A Java string's length counts UTF-16 code units. Some Unicode characters occupy two code units, so taking a fixed number of units with substring can cut a surrogate pair in half. If your application contract specifies a maximum number of Unicode code points, calculate the substring boundary using that same unit.

Prerequisites: JDK 21 with javac and java on the command path. The example uses no dependencies and was compiled and executed on OpenJDK 21.0.13.0.3. It works entirely with in-memory strings.

Count first, then locate the boundary

The function counts code points, clamps the requested limit to the available count and translates that count into a UTF-16 index. The JDK 21 String documentation defines codePointCount, offsetByCodePoints and substring. Negative limits are rejected rather than silently treated as zero.

import java.util.Objects;

public class CodePointPrefix {
    static String prefix(String text, int limit) {
        Objects.requireNonNull(text, "text");
        if (limit < 0) throw new IllegalArgumentException("negative limit");
        int count = text.codePointCount(0, text.length());
        int end = text.offsetByCodePoints(0, Math.min(limit, count));
        return text.substring(0, end);
    }
    static void check(boolean condition) {
        if (!condition) throw new AssertionError();
    }
    public static void main(String[] args) {
        String demo = "A\uD83D\uDE80B";
        check(prefix(demo, 2).equals("A\uD83D\uDE80"));
        check(prefix(demo, 0).isEmpty());
        check(prefix(demo, 9).equals(demo));
        check(prefix("", 3).isEmpty());
        // A combining accent is a separate code point.
        check(prefix("e\u0301", 1).equals("e"));
        try {
            prefix(demo, -1);
            throw new AssertionError("negative limit accepted");
        } catch (IllegalArgumentException expected) { }
        System.out.println("prefix code points=" + prefix(demo, 2).codePointCount(0, 3));
        System.out.println("checks passed");
    }
}

Compile and run

Save the complete code in CodePointPrefix.java. The filename must match the public class name.

javac CodePointPrefix.java
java CodePointPrefix

Expected output:

prefix code points=2
checks passed

The demonstration text contains A, a rocket character and B. Its prefix of two code points occupies three UTF-16 code units. The equality check verifies that both halves of the rocket's surrogate pair survive. The custom check method always runs; it does not depend on enabling Java assertions with a command-line switch.

Choose the right text unit

A code-point limit is useful for a defined interchange or application rule, but it is not a general visible-character limit. The combining-accent fixture intentionally returns an unaccented e when asked for one code point. Emoji sequences can also contain multiple code points. For UI truncation, establish grapheme-cluster requirements and test them separately.

This helper is also not a byte-budget implementation. A limit in UTF-8 bytes requires a different calculation. It does not normalize text, add an ellipsis or validate that every surrogate in an input string is paired. Java's documented code-point traversal treats an unpaired surrogate as a single unit for these operations.

Troubleshooting

If the class cannot be found, run the commands from the directory containing the source and generated class file. Unicode escapes keep the source independent of an editor's ability to enter the rocket directly. If a display requirement still produces broken-looking text, inspect whether the requirement concerns code points, grapheme clusters or rendered width.

Continue with Java tutorials.