Java Natural Language Character Substring Implementation

Stanadrd Java String.substring methods may not properly handle Unicode charactesr that exceed two chars, potentially causing issues like symbols. To address this problem, I developed a solution inspired by Apache Commons StringUtils.

The implementation considers multi-language character boundaries:


    /**
     * Natural language substring method similar to {@link StringUtils#substring(String, int, int)}
     * Handles multi-language character extraction problems for use with nvarchar type field truncation
     * @param inputText string to extract
     * @param beginPos starting position (inclusive)
     * @param finishPos ending position (exclusive)
     * @return processed substring
     */
    public static String extractNaturalText(String inputText, int beginPos, int finishPos) {
        if (inputText == null) {
            return null;
        }

        // process negative values
        int totalCodePoints = inputText.codePointCount(0, inputText.length());
        if (finishPos < 0) {
            finishPos = totalCodePoints + finishPos;
        }
        if (beginPos < 0) {
            beginPos = totalCodePoints + beginPos;
        }

        // validate range
        if (finishPos > totalCodePoints) {
            finishPos = totalCodePoints;
        }

        // return empty if invalid range
        if (beginPos > finishPos) {
            return "";
        }

        if (beginPos < 0) {
            beginPos = 0;
        }
        if (finishPos < 0) {
            finishPos = 0;
        }

        StringBuilder outputBuilder = new StringBuilder();
        int counter = 0;
        int index = 0;
        
        if (beginPos > 0) {
            // skip initial characters
            for (; index < inputText.length() && counter < beginPos; index++, counter++) {
                if (Character.isHighSurrogate(inputText.charAt(index))) {
                    index++;
                }
            }
        }
        
        if (finishPos < totalCodePoints) {
            for (; index < inputText.length() && counter < finishPos; index++, counter++) {
                outputBuilder.append(inputText.charAt(index));
                if (Character.isHighSurrogate(inputText.charAt(index))) {
                    outputBuilder.append(inputText.charAt(++index));
                }
            }
        } else {
            return inputText.substring(index, inputText.length());
        }
        return outputBuilder.toString();
    }

Test cases:


        // "\uD83D\uDF01" represents 🜁
        // "\uD801\uDC00" represents 𐐀
        String sampleData = "🜁𐐀🜁𐐀𐐀🜁🜁𐐀𐐀𐐀";
        System.out.println(TextProcessor.extractNaturalText(sampleData, 0, 1));
        System.out.println(TextProcessor.extractNaturalText(sampleData, 0, 3));
        System.out.println(TextProcessor.extractNaturalText(sampleData, 1, 3));
        System.out.println(TextProcessor.extractNaturalText(sampleData, 4, 8));
        System.out.println(TextProcessor.extractNaturalText(sampleData, 4, 100));
        System.out.println(sampleData.substring(1, 3));

The results demonstrate proper handling of multi-byte Unicode characters, ensuring expected behavior when working with international text.

Tags: java unicode string-processing character-encoding text-manipulation

Posted on Sun, 02 Aug 2026 17:01:52 +0000 by colmtourque