CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-commons

Common classes used across Spring AI providing document processing, text transformation, embedding utilities, observability support, and tokenization capabilities for AI application development

Overview
Eval results
Files

utilities.mddocs/reference/

Utilities

Utility classes provide helper functions for JSON processing, string parsing, resource handling, logging with data classification, and template rendering.

Overview

The utilities layer consists of:

  • JacksonUtils - Jackson module utilities
  • ParsingUtils - String parsing utilities
  • ResourceUtils - Resource loading utilities
  • LoggingMarkers - SLF4J markers for data classification
  • TemplateRenderer - Template rendering interface

JacksonUtils

Jackson utility methods for module instantiation.

package org.springframework.ai.util;

import com.fasterxml.jackson.databind.Module;
import java.util.List;

abstract class JacksonUtils {
    /**
     * Instantiate well-known Jackson modules.
     * Attempts to instantiate:
     * - Jdk8Module (Java 8 types like Optional)
     * - JavaTimeModule (Java 8 time types)
     * - ParameterNamesModule (parameter name discovery)
     * - KotlinModule (Kotlin support)
     *
     * Only modules available on classpath are instantiated.
     * @return list of instantiated modules (empty list if no modules found)
     */
    static List<Module> instantiateAvailableModules();
}

Usage Examples

import org.springframework.ai.util.JacksonUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.Module;
import java.util.List;

// Get available Jackson modules
List<Module> modules = JacksonUtils.instantiateAvailableModules();

System.out.println("Found " + modules.size() + " Jackson modules");

// Register modules with ObjectMapper
ObjectMapper mapper = new ObjectMapper();
modules.forEach(mapper::registerModule);

// Now mapper supports Java 8 types, time types, etc.
// Example usage
class Data {
    private Optional<String> optionalField;
    private LocalDateTime timestamp;
    private List<Integer> numbers;

    // getters/setters
}

Data data = new Data();
data.setOptionalField(Optional.of("value"));
data.setTimestamp(LocalDateTime.now());
data.setNumbers(List.of(1, 2, 3));

String json = mapper.writeValueAsString(data);
System.out.println(json);

Data deserialized = mapper.readValue(json, Data.class);

Module Discovery

import org.springframework.ai.util.JacksonUtils;
import com.fasterxml.jackson.databind.Module;
import java.util.List;

// Discover and log available modules
List<Module> modules = JacksonUtils.instantiateAvailableModules();

for (Module module : modules) {
    System.out.println("Module: " + module.getModuleName());
    System.out.println("Version: " + module.version());
}

// Typical output (if all dependencies are present):
// Module: jackson-module-parameter-names
// Module: jackson-datatype-jdk8
// Module: jackson-datatype-jsr310
// Module: jackson-module-kotlin (if Kotlin is on classpath)

ParsingUtils

String parsing utilities for camel case handling.

package org.springframework.ai.util;

import java.util.List;

abstract class ParsingUtils {
    /**
     * Split camel case string into words.
     * Example: "myVariableName" -> ["my", "Variable", "Name"]
     * @param source source string in camel case
     * @return list of words
     */
    static List<String> splitCamelCase(String source);

    /**
     * Split camel case string into lowercase words.
     * Example: "myVariableName" -> ["my", "variable", "name"]
     * @param source source string in camel case
     * @return list of lowercase words
     */
    static List<String> splitCamelCaseToLower(String source);

    /**
     * Re-concatenate camel case with delimiter.
     * Example: "myVariableName", "-" -> "my-variable-name"
     * @param source source string in camel case
     * @param delimiter delimiter to use
     * @return concatenated string
     */
    static String reConcatenateCamelCase(String source, String delimiter);
}

Usage Examples

import org.springframework.ai.util.ParsingUtils;
import java.util.List;

// Split camel case
String camelCase = "myVariableName";
List<String> words = ParsingUtils.splitCamelCase(camelCase);
System.out.println(words);  // [my, Variable, Name]

// Split to lowercase
List<String> lowerWords = ParsingUtils.splitCamelCaseToLower(camelCase);
System.out.println(lowerWords);  // [my, variable, name]

// Re-concatenate with delimiter
String kebabCase = ParsingUtils.reConcatenateCamelCase(camelCase, "-");
System.out.println(kebabCase);  // my-variable-name

String snakeCase = ParsingUtils.reConcatenateCamelCase(camelCase, "_");
System.out.println(snakeCase);  // my_variable_name

String spaced = ParsingUtils.reConcatenateCamelCase(camelCase, " ");
System.out.println(spaced);  // my variable name

// More examples
String className = "DocumentTransformer";
System.out.println(ParsingUtils.reConcatenateCamelCase(className, "-"));
// document-transformer

String methodName = "getFormattedContent";
System.out.println(ParsingUtils.reConcatenateCamelCase(methodName, "_"));
// get_formatted_content

Practical Applications

import org.springframework.ai.util.ParsingUtils;

/**
 * Convert Java naming conventions to different formats.
 */
class NamingConverter {
    /**
     * Convert Java class name to URL path.
     */
    public static String toUrlPath(String className) {
        return "/" + ParsingUtils.reConcatenateCamelCase(className, "-").toLowerCase();
    }

    /**
     * Convert method name to property key.
     */
    public static String toPropertyKey(String methodName) {
        // Remove "get" or "set" prefix
        String name = methodName;
        if (name.startsWith("get") || name.startsWith("set")) {
            name = name.substring(3);
        }

        return ParsingUtils.reConcatenateCamelCase(name, ".").toLowerCase();
    }

    /**
     * Convert to human-readable label.
     */
    public static String toLabel(String identifier) {
        List<String> words = ParsingUtils.splitCamelCaseToLower(identifier);
        return words.stream()
            .map(word -> word.substring(0, 1).toUpperCase() + word.substring(1))
            .collect(java.util.stream.Collectors.joining(" "));
    }
}

// Usage
System.out.println(NamingConverter.toUrlPath("UserProfile"));
// /user-profile

System.out.println(NamingConverter.toPropertyKey("getUserName"));
// user.name

System.out.println(NamingConverter.toLabel("firstName"));
// First Name

ResourceUtils

Resource utility methods for loading content.

package org.springframework.ai.util;

abstract class ResourceUtils {
    /**
     * Get resource content as UTF-8 string.
     * Supports classpath:, file:, and http(s): URIs.
     * @param uri resource URI
     * @return resource content as string
     */
    static String getText(String uri);
}

Usage Examples

import org.springframework.ai.util.ResourceUtils;

// Load from classpath
String classpathContent = ResourceUtils.getText("classpath:data/template.txt");
System.out.println("Classpath content: " + classpathContent);

// Load from file system
String fileContent = ResourceUtils.getText("file:/path/to/document.txt");
System.out.println("File content: " + fileContent);

// Load from URL
String urlContent = ResourceUtils.getText("https://example.com/data.txt");
System.out.println("URL content: " + urlContent);

// Use in prompt templates
String promptTemplate = ResourceUtils.getText("classpath:prompts/system-prompt.txt");
String userQuery = "What is Spring AI?";
String fullPrompt = promptTemplate.replace("{query}", userQuery);

// Load configuration
String configJson = ResourceUtils.getText("classpath:config/ai-config.json");
// Parse JSON configuration

Template Loading Pattern

import org.springframework.ai.util.ResourceUtils;
import java.util.Map;

/**
 * Load and process prompt templates from resources.
 */
class PromptTemplateLoader {
    /**
     * Load template and replace variables.
     */
    public static String loadAndFormat(String templateUri, Map<String, String> variables) {
        String template = ResourceUtils.getText(templateUri);

        String result = template;
        for (Map.Entry<String, String> entry : variables.entrySet()) {
            String placeholder = "{" + entry.getKey() + "}";
            result = result.replace(placeholder, entry.getValue());
        }

        return result;
    }
}

// Usage
Map<String, String> vars = Map.of(
    "name", "Spring AI",
    "version", "1.1.2",
    "language", "Java"
);

String prompt = PromptTemplateLoader.loadAndFormat(
    "classpath:prompts/introduction.txt",
    vars
);

System.out.println(prompt);

LoggingMarkers

Predefined SLF4J markers for data classification in logs.

package org.springframework.ai.util;

import org.slf4j.Marker;

abstract class LoggingMarkers {
    /**
     * Marker for sensitive business data.
     * Use for data that should be protected within organization.
     */
    static final Marker SENSITIVE_DATA_MARKER;

    /**
     * Marker for restricted data (credentials, secrets, IP).
     * Use for data that should never be logged in production.
     */
    static final Marker RESTRICTED_DATA_MARKER;

    /**
     * Marker for regulated data (PCI, PHI, PII).
     * Use for data subject to compliance regulations.
     */
    static final Marker REGULATED_DATA_MARKER;

    /**
     * Marker for public data.
     * Use for data safe to log in all environments.
     */
    static final Marker PUBLIC_DATA_MARKER;
}

Usage Examples

import org.springframework.ai.util.LoggingMarkers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class SecureLoggingExample {
    private static final Logger logger = LoggerFactory.getLogger(SecureLoggingExample.class);

    public void processUserData(String userId, String email, String apiKey,
                                 String publicInfo) {
        // Public data - safe to log
        logger.info(LoggingMarkers.PUBLIC_DATA_MARKER,
            "Processing request for user ID: {}", userId);

        // Sensitive business data - log carefully
        logger.debug(LoggingMarkers.SENSITIVE_DATA_MARKER,
            "User email: {}", email);

        // Restricted data - should never be logged in production
        logger.trace(LoggingMarkers.RESTRICTED_DATA_MARKER,
            "API key validation (first 4 chars): {}...", apiKey.substring(0, 4));

        // Regulated data - subject to compliance
        logger.debug(LoggingMarkers.REGULATED_DATA_MARKER,
            "Processing PII for user: {}", userId);

        // Public information
        logger.info(LoggingMarkers.PUBLIC_DATA_MARKER,
            "Public info: {}", publicInfo);
    }
}

Logback Configuration

Configure Logback to filter based on markers:

<!-- logback.xml -->
<configuration>
    <!-- Console appender - exclude sensitive data -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
            <evaluator>
                <matcher>
                    <Name>RESTRICTED_FILTER</Name>
                    <Value>RESTRICTED_DATA</Value>
                </matcher>
                <expression>marker != null &amp;&amp; marker.contains("RESTRICTED_DATA")</expression>
            </evaluator>
            <OnMatch>DENY</OnMatch>
            <OnMismatch>NEUTRAL</OnMismatch>
        </filter>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Secure file appender - include all data -->
    <appender name="SECURE_FILE" class="ch.qos.logback.core.FileAppender">
        <file>logs/secure.log</file>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%marker] - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="SECURE_FILE" />
    </root>
</configuration>

Marker-Based Security Policy

import org.springframework.ai.util.LoggingMarkers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implement security-conscious logging throughout application.
 */
class SecurityAwareLogger {
    private final Logger logger;
    private final boolean productionMode;

    public SecurityAwareLogger(Class<?> clazz, boolean productionMode) {
        this.logger = LoggerFactory.getLogger(clazz);
        this.productionMode = productionMode;
    }

    /**
     * Log public information (always safe).
     */
    public void logPublic(String message, Object... args) {
        logger.info(LoggingMarkers.PUBLIC_DATA_MARKER, message, args);
    }

    /**
     * Log sensitive data (only in non-production).
     */
    public void logSensitive(String message, Object... args) {
        if (!productionMode) {
            logger.debug(LoggingMarkers.SENSITIVE_DATA_MARKER, message, args);
        }
    }

    /**
     * Log restricted data (only in development with explicit flag).
     */
    public void logRestricted(String message, Object... args) {
        if (!productionMode && logger.isTraceEnabled()) {
            logger.trace(LoggingMarkers.RESTRICTED_DATA_MARKER, message, args);
        }
    }

    /**
     * Log regulated data (with compliance awareness).
     */
    public void logRegulated(String message, Object... args) {
        // Only log if compliance logging is enabled
        if (isComplianceLoggingEnabled()) {
            logger.debug(LoggingMarkers.REGULATED_DATA_MARKER, message, args);
        }
    }

    private boolean isComplianceLoggingEnabled() {
        // Check if compliance logging is enabled
        return false;  // Placeholder
    }
}

// Usage
SecurityAwareLogger secureLogger = new SecurityAwareLogger(
    MyService.class,
    isProduction()
);

secureLogger.logPublic("Request processed successfully");
secureLogger.logSensitive("User email: {}", userEmail);
secureLogger.logRestricted("API key: {}", apiKey);
secureLogger.logRegulated("PII data: {}", personalInfo);

TemplateRenderer

Template rendering interface for dynamic content generation.

package org.springframework.ai.template;

import java.util.Map;
import java.util.function.BiFunction;

interface TemplateRenderer extends BiFunction<String, Map<String, Object>, String> {
    /**
     * Render template with variables.
     * @param template template string
     * @param variables map of variable names to values
     * @return rendered string
     */
    String apply(String template, Map<String, Object> variables);
}

NoOpTemplateRenderer

package org.springframework.ai.template;

import java.util.Map;

class NoOpTemplateRenderer implements TemplateRenderer {
    /**
     * Create no-op renderer.
     */
    NoOpTemplateRenderer();

    /**
     * Return template unchanged.
     * Variables are ignored.
     * @param template template string (must not be null or empty)
     * @param variables variables (ignored)
     * @return template unchanged
     * @throws IllegalArgumentException if template is null or empty
     */
    String apply(String template, Map<String, Object> variables);
}

ValidationMode

package org.springframework.ai.template;

enum ValidationMode {
    /**
     * Throw exception on validation errors (default).
     */
    THROW,

    /**
     * Log warning on validation errors.
     */
    WARN,

    /**
     * Skip validation.
     */
    NONE;
}

Usage Examples

import org.springframework.ai.template.TemplateRenderer;
import org.springframework.ai.template.NoOpTemplateRenderer;
import java.util.Map;

// No-op renderer (pass-through)
TemplateRenderer noOpRenderer = new NoOpTemplateRenderer();

String template = "Hello, {name}! Welcome to {product}.";
Map<String, Object> variables = Map.of(
    "name", "Alice",
    "product", "Spring AI"
);

String result = noOpRenderer.apply(template, variables);
System.out.println(result);
// Output: "Hello, {name}! Welcome to {product}." (unchanged)

// Custom renderer implementation
TemplateRenderer simpleRenderer = (template, vars) -> {
    String result = template;
    for (Map.Entry<String, Object> entry : vars.entrySet()) {
        String placeholder = "{" + entry.getKey() + "}";
        result = result.replace(placeholder, entry.getValue().toString());
    }
    return result;
};

String rendered = simpleRenderer.apply(template, variables);
System.out.println(rendered);
// Output: "Hello, Alice! Welcome to Spring AI."

Custom Template Renderer

import org.springframework.ai.template.TemplateRenderer;
import org.springframework.ai.template.ValidationMode;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Custom template renderer with validation.
 */
class CustomTemplateRenderer implements TemplateRenderer {
    private final ValidationMode validationMode;
    private final Pattern placeholderPattern = Pattern.compile("\\{([^}]+)\\}");

    public CustomTemplateRenderer(ValidationMode validationMode) {
        this.validationMode = validationMode;
    }

    @Override
    public String apply(String template, Map<String, Object> variables) {
        // Validate if needed
        if (validationMode == ValidationMode.THROW) {
            validateTemplate(template, variables);
        }

        // Render template
        StringBuffer result = new StringBuffer();
        Matcher matcher = placeholderPattern.matcher(template);

        while (matcher.find()) {
            String key = matcher.group(1);
            Object value = variables.get(key);

            if (value != null) {
                matcher.appendReplacement(result, value.toString());
            } else if (validationMode == ValidationMode.WARN) {
                System.err.println("Warning: Missing variable: " + key);
                matcher.appendReplacement(result, "{" + key + "}");
            } else {
                matcher.appendReplacement(result, "{" + key + "}");
            }
        }

        matcher.appendTail(result);
        return result.toString();
    }

    private void validateTemplate(String template, Map<String, Object> variables) {
        Matcher matcher = placeholderPattern.matcher(template);

        while (matcher.find()) {
            String key = matcher.group(1);
            if (!variables.containsKey(key)) {
                throw new IllegalArgumentException(
                    "Missing required variable: " + key
                );
            }
        }
    }
}

// Usage
TemplateRenderer strictRenderer = new CustomTemplateRenderer(ValidationMode.THROW);
TemplateRenderer lenientRenderer = new CustomTemplateRenderer(ValidationMode.WARN);

String template = "Hello, {name}! You have {count} messages.";

try {
    // This will throw exception (missing "count")
    strictRenderer.apply(template, Map.of("name", "Alice"));
} catch (IllegalArgumentException e) {
    System.err.println(e.getMessage());
}

// This will warn but continue
String result = lenientRenderer.apply(template, Map.of("name", "Alice"));
System.out.println(result);
// Output: "Hello, Alice! You have {count} messages."
// Warning: Missing variable: count

Practical Integration

Combining Utilities

import org.springframework.ai.util.ResourceUtils;
import org.springframework.ai.util.ParsingUtils;
import org.springframework.ai.util.LoggingMarkers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;

/**
 * Example combining multiple utilities.
 */
class IntegratedUtilities {
    private static final Logger logger = LoggerFactory.getLogger(IntegratedUtilities.class);

    public String loadAndProcessTemplate(String resourceUri, Map<String, String> variables) {
        // Load template using ResourceUtils
        logger.info(LoggingMarkers.PUBLIC_DATA_MARKER,
            "Loading template from: {}", resourceUri);

        String template = ResourceUtils.getText(resourceUri);

        // Process template
        String result = template;
        for (Map.Entry<String, String> entry : variables.entrySet()) {
            // Convert camelCase keys to snake_case placeholders
            String placeholder = "{" +
                ParsingUtils.reConcatenateCamelCase(entry.getKey(), "_") + "}";
            result = result.replace(placeholder, entry.getValue());
        }

        logger.debug(LoggingMarkers.SENSITIVE_DATA_MARKER,
            "Template processed with {} variables", variables.size());

        return result;
    }
}

// Usage
IntegratedUtilities utils = new IntegratedUtilities();

Map<String, String> vars = Map.of(
    "userName", "Alice",
    "documentCount", "42"
);

String result = utils.loadAndProcessTemplate(
    "classpath:templates/welcome.txt",
    vars
);
// Template placeholders: {user_name}, {document_count}

Best Practices

  1. Jackson Modules: Register modules early in application lifecycle
  2. Camel Case Parsing: Use for API naming conventions and code generation
  3. Resource Loading: Prefer classpath resources for portability
  4. Logging Markers: Always classify data sensitivity in logs
  5. Template Rendering: Validate templates before production use

Thread Safety and Performance

Thread Safety:

  • JacksonUtils: Thread-safe (static utility methods)
  • ParsingUtils: Thread-safe (static utility methods)
  • ResourceUtils: Thread-safe for reading (stateless)
  • LoggingMarkers: Thread-safe (static final markers)
  • TemplateRenderer: Depends on implementation (NoOpTemplateRenderer is thread-safe)

Performance:

  • JacksonUtils.instantiateAvailableModules(): Expensive, call once at startup
  • ParsingUtils: O(n) where n is string length
  • ResourceUtils.getText(): I/O bound, depends on resource location
  • Template rendering: O(n) where n is template size

Error Handling

Common Exceptions:

  • IOException: Resource not found, network errors (ResourceUtils)
  • IllegalArgumentException: Invalid parameters (null/empty strings)
  • ClassNotFoundException: Module not on classpath (JacksonUtils, silently skipped)
  • RuntimeException: Unexpected errors (encoding issues, resource access failures)

Edge Cases:

// JacksonUtils - missing modules
List<Module> modules = JacksonUtils.instantiateAvailableModules();
// Returns empty list if no modules found (not an error)

// ParsingUtils - edge cases
ParsingUtils.splitCamelCase("lowercase");  // Returns ["lowercase"]
ParsingUtils.splitCamelCase("UPPERCASE");  // Returns ["UPPERCASE"]
ParsingUtils.splitCamelCase("");           // Returns empty list
ParsingUtils.reConcatenateCamelCase("", "-");  // Returns ""

// ResourceUtils - missing resource
try {
    String text = ResourceUtils.getText("classpath:missing.txt");
    // Throws IOException wrapped in RuntimeException
} catch (RuntimeException e) {
    // Handle missing resource
}

// ResourceUtils - network timeout
try {
    String text = ResourceUtils.getText("https://slow-server.com/data.txt");
    // May timeout or throw IOException
} catch (RuntimeException e) {
    // Handle network error
}

// LoggingMarkers - null safety
Logger logger = // ...
logger.info(null, "message");  // Marker can be null (no marker applied)

// TemplateRenderer - validation
NoOpTemplateRenderer renderer = new NoOpTemplateRenderer();
try {
    renderer.apply(null, variables);  // Throws IllegalArgumentException
} catch (IllegalArgumentException e) {
    // Handle null template
}

try {
    renderer.apply("", variables);  // Throws IllegalArgumentException
} catch (IllegalArgumentException e) {
    // Handle empty template
}

Best Practices

  1. Jackson Modules: Call instantiateAvailableModules() once at startup
  2. Resource Loading: Handle I/O exceptions, validate resource existence
  3. Camel Case Parsing: Use for API naming conventions and code generation
  4. Logging Markers: Always classify data sensitivity in logs
  5. Template Rendering: Validate templates before production use
  6. Charset Handling: Specify charset explicitly for ResourceUtils
  7. Security: Never log sensitive data without appropriate markers

Security Considerations

Logging Security:

// NEVER log credentials without protection
logger.info("API Key: " + apiKey);  // BAD

// Use RESTRICTED_DATA_MARKER and appropriate log filtering
logger.trace(LoggingMarkers.RESTRICTED_DATA_MARKER, "API Key: {}", apiKey);  // GOOD

// Configure logback to filter RESTRICTED_DATA in production

Resource Loading Security:

// Validate resource paths to prevent path traversal
String userInput = // ... from user
if (userInput.contains("..") || userInput.contains("~")) {
    throw new SecurityException("Invalid resource path");
}

// Use classpath resources when possible (safer than file system)
String text = ResourceUtils.getText("classpath:safe/path.txt");

Related Documentation

  • Document Model - Document processing
  • Observability - Monitoring and logging

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-ai--spring-ai-commons@1.1.0

docs

index.md

README.md

tile.json