CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

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

ONNX-based Transformer models for text embeddings within the Spring AI framework

Overview
Eval results
Files

resource-caching.mddocs/reference/

Resource Caching

The ResourceCacheService class provides automatic caching of remote Spring Resources on the local filesystem. This service is used internally by TransformersEmbeddingModel to cache ONNX models and tokenizers, avoiding repeated downloads and improving startup performance.

Capabilities

Get Cached Resource by URI

Retrieve a cached copy of a resource specified by a URI string. If the resource is not cached, it will be downloaded and cached automatically.

/**
 * Get a Resource representing the cached copy of the original resource.
 * Downloads and caches the resource if not already cached.
 * Returns original resource if URI schema is excluded from caching.
 *
 * @param originalResourceUri URI string of the resource to cache
 * @return Cached resource (FileUrlResource), or original resource if excluded
 * @throws IllegalStateException if caching fails (wraps IOException)
 */
Resource getCachedResource(String originalResourceUri);

Usage:

import org.springframework.ai.transformers.ResourceCacheService;
import org.springframework.core.io.Resource;
import java.io.InputStream;

ResourceCacheService cacheService = new ResourceCacheService();

// Cache a remote model
Resource cachedModel = cacheService.getCachedResource(
    "https://example.com/models/model.onnx"
);

// Cache a remote tokenizer
Resource cachedTokenizer = cacheService.getCachedResource(
    "https://huggingface.co/models/tokenizer.json"
);

// Use the cached resource
try (InputStream is = cachedModel.getInputStream()) {
    // Read model data from cached file
    byte[] data = is.readAllBytes();
    System.out.println("Model size: " + data.length + " bytes");
}

Behavior:

// First call: Downloads and caches the resource
ResourceCacheService cache = new ResourceCacheService();
long start = System.currentTimeMillis();
Resource resource1 = cache.getCachedResource("https://example.com/large-model.onnx");
long firstCallTime = System.currentTimeMillis() - start;
// firstCallTime: 5000-30000ms (depends on network and file size)

// Subsequent calls: Returns cached copy from local filesystem
start = System.currentTimeMillis();
Resource resource2 = cache.getCachedResource("https://example.com/large-model.onnx");
long secondCallTime = System.currentTimeMillis() - start;
// secondCallTime: <10ms (local filesystem access)

// Cache persists across application restarts
// New instance, same cache directory
ResourceCacheService cache2 = new ResourceCacheService();
start = System.currentTimeMillis();
Resource resource3 = cache2.getCachedResource("https://example.com/large-model.onnx");
long thirdCallTime = System.currentTimeMillis() - start;
// thirdCallTime: <10ms (cache from previous run)

Excluded Schemas:

// file: and classpath: resources are not cached (already local)
ResourceCacheService cache = new ResourceCacheService();

// File resource - not cached
Resource fileResource = cache.getCachedResource("file:///path/to/local.onnx");
// Returns original FileSystemResource (not cached)

// Classpath resource - not cached
Resource classpathResource = cache.getCachedResource("classpath:/models/model.onnx");
// Returns original ClassPathResource (not cached)

// HTTP/HTTPS resource - cached
Resource httpResource = cache.getCachedResource("https://example.com/model.onnx");
// Downloads and returns FileUrlResource pointing to cached file

// FTP resource - cached (if supported)
Resource ftpResource = cache.getCachedResource("ftp://example.com/model.onnx");
// Downloads and returns cached resource

Edge Cases:

// Null URI - behavior depends on implementation
try {
    cache.getCachedResource(null);
} catch (NullPointerException | IllegalArgumentException e) {
    // Likely throws exception
}

// Empty URI
try {
    cache.getCachedResource("");
} catch (IllegalArgumentException e) {
    // May throw exception
}

// Invalid URI - exception during download
try {
    cache.getCachedResource("https://invalid-domain-12345.com/model.onnx");
} catch (IllegalStateException e) {
    // Wraps IOException from network error
}

// Resource that doesn't exist (404)
try {
    cache.getCachedResource("https://example.com/nonexistent.onnx");
} catch (IllegalStateException e) {
    // Wraps IOException from 404 error
}

// Very large resource (may cause OOM or disk full)
try {
    cache.getCachedResource("https://example.com/huge-10gb-model.onnx");
} catch (OutOfMemoryError e) {
    // May run out of memory during download
} catch (IllegalStateException e) {
    // Or disk full error
}

// URI with special characters
Resource resource = cache.getCachedResource(
    "https://example.com/models/model%20with%20spaces.onnx"
);
// URL encoding handled correctly

// URI with query parameters
Resource resourceWithQuery = cache.getCachedResource(
    "https://example.com/model.onnx?version=1.2&token=abc"
);
// Query parameters preserved in cache key

// URI with fragment
Resource resourceWithFragment = cache.getCachedResource(
    "https://example.com/model.onnx#section"
);
// Fragment appended to cached filename: model.onnx_section

Get Cached Resource from Spring Resource

Retrieve a cached copy of a Spring Resource object. Provides the same caching behavior as the URI-based method.

/**
 * Get a Resource representing the cached copy of the original resource.
 * Downloads and caches the resource if not already cached.
 * Returns original resource if URI schema is excluded from caching.
 *
 * @param originalResource Spring Resource to cache
 * @return Cached resource (FileUrlResource), or original resource if excluded
 * @throws IllegalStateException if caching fails (wraps IOException)
 */
Resource getCachedResource(Resource originalResource);

Usage:

import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import java.net.URL;

ResourceCacheService cacheService = new ResourceCacheService();
DefaultResourceLoader loader = new DefaultResourceLoader();

// Load resource using Spring's resource loader
Resource original = loader.getResource("https://example.com/model.onnx");

// Get cached copy
Resource cached = cacheService.getCachedResource(original);

// Use cached resource
byte[] content = cached.getContentAsByteArray();
System.out.println("Cached model size: " + content.length);

// Using UrlResource directly
UrlResource urlResource = new UrlResource(new URL("https://example.com/tokenizer.json"));
Resource cachedTokenizer = cacheService.getCachedResource(urlResource);

Excluded Schemas: Resources with file: or classpath: URI schemes are not cached, as they are already local.

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;

ResourceCacheService cache = new ResourceCacheService();

// ClassPathResource - not cached
ClassPathResource classpathResource = new ClassPathResource("/models/model.onnx");
Resource result1 = cache.getCachedResource(classpathResource);
assert result1 == classpathResource; // Same instance returned

// FileSystemResource - not cached
FileSystemResource fileResource = new FileSystemResource("/path/to/model.onnx");
Resource result2 = cache.getCachedResource(fileResource);
assert result2 == fileResource; // Same instance returned

// UrlResource - cached
UrlResource urlResource = new UrlResource(new URL("https://example.com/model.onnx"));
Resource result3 = cache.getCachedResource(urlResource);
assert result3 != urlResource; // Different instance (cached FileUrlResource)

Edge Cases:

// Null resource
try {
    cache.getCachedResource((Resource) null);
} catch (NullPointerException | IllegalArgumentException e) {
    // Likely throws exception
}

// Resource that doesn't support URI
Resource customResource = new AbstractResource() {
    @Override
    public String getDescription() {
        return "custom resource";
    }
    @Override
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(new byte[0]);
    }
    @Override
    public URI getURI() throws IOException {
        throw new UnsupportedOperationException();
    }
};
try {
    cache.getCachedResource(customResource);
} catch (IllegalStateException e) {
    // May fail if URI cannot be determined
}

// Resource with relative URI
// Behavior depends on Resource implementation

Configure Excluded URI Schemas

Customize which URI schemas should be excluded from caching. By default, file and classpath resources are not cached.

/**
 * Override the excluded URI schemas list.
 * Resources with these schemas are not cached and returned as-is.
 *
 * @param excludedUriSchemas List of URI schemas to exclude (must not be null)
 * @throws IllegalArgumentException if excludedUriSchemas is null
 */
void setExcludedUriSchemas(List<String> excludedUriSchemas);

Usage:

import java.util.List;
import java.util.ArrayList;

ResourceCacheService cacheService = new ResourceCacheService();

// Only exclude file: resources
cacheService.setExcludedUriSchemas(List.of("file"));

// Exclude additional schemas
cacheService.setExcludedUriSchemas(List.of("file", "classpath", "jar"));

// Cache everything (not recommended)
cacheService.setExcludedUriSchemas(List.of());

// Using mutable list for dynamic configuration
List<String> excludedSchemas = new ArrayList<>();
excludedSchemas.add("file");
excludedSchemas.add("classpath");
cacheService.setExcludedUriSchemas(excludedSchemas);

// Later: add more schemas
excludedSchemas.add("ftp");
cacheService.setExcludedUriSchemas(excludedSchemas);

Default: List.of("file", "classpath")

Common Schemas:

// file: Local filesystem paths
// classpath: Resources on the Java classpath
// http: HTTP URLs (cached by default)
// https: HTTPS URLs (cached by default)
// ftp: FTP URLs (cached by default if supported)
// jar: Resources inside JAR files

Examples:

ResourceCacheService cache = new ResourceCacheService();

// Example 1: Cache classpath resources too (unusual)
cache.setExcludedUriSchemas(List.of("file"));
Resource classpathResource = cache.getCachedResource("classpath:/models/model.onnx");
// Now cached to filesystem

// Example 2: Don't cache HTTP (only HTTPS)
cache.setExcludedUriSchemas(List.of("file", "classpath", "http"));
Resource httpResource = cache.getCachedResource("http://example.com/model.onnx");
// Not cached (returned as-is)
Resource httpsResource = cache.getCachedResource("https://example.com/model.onnx");
// Cached normally

// Example 3: Cache everything
cache.setExcludedUriSchemas(List.of());
Resource fileResource = cache.getCachedResource("file:///path/to/model.onnx");
// Even file: resources are cached (creates duplicate)

// Example 4: Exclude custom schemes
cache.setExcludedUriSchemas(List.of("file", "classpath", "s3", "gs"));
// s3: and gs: resources not cached (if supported by Resource implementation)

Edge Cases:

// Null list - throws IllegalArgumentException
try {
    cache.setExcludedUriSchemas(null);
} catch (IllegalArgumentException e) {
    // Exception: excluded schemas must not be null
}

// Empty list - cache everything
cache.setExcludedUriSchemas(List.of());
// No schemas excluded

// Case sensitivity
cache.setExcludedUriSchemas(List.of("FILE", "CLASSPATH"));
// Schemas are case-insensitive in URIs, but implementation may be case-sensitive
// Better to use lowercase: List.of("file", "classpath")

// Schema with trailing colon
cache.setExcludedUriSchemas(List.of("file:", "classpath:"));
// May not work - use schema name without colon: "file", "classpath"

// Changing exclusions after caching
cache.setExcludedUriSchemas(List.of("file", "classpath"));
Resource cached1 = cache.getCachedResource("https://example.com/model.onnx");
// Cached

cache.setExcludedUriSchemas(List.of("file", "classpath", "https"));
// Now https is excluded
Resource cached2 = cache.getCachedResource("https://example.com/model.onnx");
// Returns original, not cached version (even though already in cache)

Delete Cache Folder

Clear all cached resources by deleting and recreating the cache directory. Useful for clearing space or forcing re-download of resources.

/**
 * Delete the cache folder and recreate it empty.
 * All cached resources are removed permanently.
 */
void deleteCacheFolder();

Usage:

import java.io.File;

ResourceCacheService cacheService = new ResourceCacheService();

// Cache some resources
cacheService.getCachedResource("https://example.com/model1.onnx");
cacheService.getCachedResource("https://example.com/model2.onnx");

// Clear all cached resources
cacheService.deleteCacheFolder();

// Cache will be rebuilt on next getCachedResource call
Resource fresh = cacheService.getCachedResource(
    "https://example.com/model1.onnx"
);
// Downloads again (cache was cleared)

Effect: Removes all cached files and recreates an empty cache directory.

Use Cases:

// 1. Free up disk space
ResourceCacheService cache = new ResourceCacheService("/large/cache");
// Cache has grown to several GB
cache.deleteCacheFolder();
// Disk space freed

// 2. Force refresh of models
ResourceCacheService cache = new ResourceCacheService();
// Model was updated on server
cache.deleteCacheFolder();
// Next access downloads new version

// 3. Clean up after testing
@After
public void cleanup() {
    cacheService.deleteCacheFolder();
}

// 4. Periodic maintenance
@Scheduled(cron = "0 0 0 * * SUN") // Every Sunday at midnight
public void weeklyCleanup() {
    cacheService.deleteCacheFolder();
}

Edge Cases:

// Delete non-existent cache - no error
ResourceCacheService cache = new ResourceCacheService("/path/to/nonexistent");
cache.deleteCacheFolder(); // No exception, creates empty directory

// Delete cache with no permissions
ResourceCacheService cache = new ResourceCacheService("/root/cache");
try {
    cache.deleteCacheFolder();
} catch (SecurityException e) {
    // May throw exception if directory cannot be deleted
}

// Delete cache while resources are in use
ResourceCacheService cache = new ResourceCacheService();
Resource resource = cache.getCachedResource("https://example.com/model.onnx");
InputStream is = resource.getInputStream(); // File handle open

cache.deleteCacheFolder(); // May fail on Windows (file in use)
// Better: close all resources before deleting

is.close();
cache.deleteCacheFolder(); // Now succeeds

// Concurrent deletions
// Thread 1
cache.deleteCacheFolder();

// Thread 2 (at same time)
cache.deleteCacheFolder();
// May cause race condition or exception

// Delete and immediately use
cache.deleteCacheFolder();
Resource resource = cache.getCachedResource("https://example.com/model.onnx");
// Works correctly - cache directory is recreated automatically

Constructors

The ResourceCacheService provides multiple constructors for different cache location configurations.

/**
 * Default constructor.
 * Uses system temp directory: {java.io.tmpdir}/spring-ai-onnx-generative
 */
public ResourceCacheService();

/**
 * Constructor with custom cache directory path.
 *
 * @param rootCacheDirectory Path to cache directory as string
 */
public ResourceCacheService(String rootCacheDirectory);

/**
 * Constructor with custom cache directory file.
 *
 * @param rootCacheDirectory File object representing cache directory
 */
public ResourceCacheService(File rootCacheDirectory);

Usage:

import java.io.File;

// Default cache location
ResourceCacheService cache1 = new ResourceCacheService();
// Location: {java.io.tmpdir}/spring-ai-onnx-generative
// Example: /tmp/spring-ai-onnx-generative (Linux)
//          C:\Users\user\AppData\Local\Temp\spring-ai-onnx-generative (Windows)

// Custom location by path
ResourceCacheService cache2 = new ResourceCacheService(
    "/var/cache/spring-ai-models"
);

// Custom location by File
File cacheDir = new File(System.getProperty("user.home"), ".spring-ai/cache");
ResourceCacheService cache3 = new ResourceCacheService(cacheDir);

// Using system properties
String userHome = System.getProperty("user.home");
ResourceCacheService cache4 = new ResourceCacheService(userHome + "/.cache/spring-ai");

// Using environment variables
String customCache = System.getenv("SPRING_AI_CACHE_DIR");
ResourceCacheService cache5 = customCache != null 
    ? new ResourceCacheService(customCache)
    : new ResourceCacheService();

Cache Directory Creation: If the specified cache directory doesn't exist, it will be created automatically during construction.

// Non-existent directory - created automatically
File newCacheDir = new File("/path/to/new/cache");
assert !newCacheDir.exists();

ResourceCacheService cache = new ResourceCacheService(newCacheDir);
// Directory is created (along with parent directories)

assert newCacheDir.exists();
assert newCacheDir.isDirectory();

Edge Cases:

// Null cache directory - behavior depends on implementation
try {
    new ResourceCacheService((String) null);
} catch (NullPointerException | IllegalArgumentException e) {
    // May throw exception
}

try {
    new ResourceCacheService((File) null);
} catch (NullPointerException | IllegalArgumentException e) {
    // May throw exception
}

// Empty string cache directory
try {
    new ResourceCacheService("");
} catch (IllegalArgumentException e) {
    // May throw exception or use current directory
}

// Relative path
ResourceCacheService cache = new ResourceCacheService("./cache");
// Works, relative to current working directory

// Directory with no write permissions
try {
    new ResourceCacheService("/root/cache");
} catch (SecurityException | IllegalStateException e) {
    // May fail during construction or first use
}

// Path to existing file (not directory)
File existingFile = new File("/path/to/file.txt");
existingFile.createNewFile();
try {
    new ResourceCacheService(existingFile);
} catch (IllegalArgumentException e) {
    // May throw exception (not a directory)
}

// Very long path
String longPath = "/very/long/path/".repeat(100);
try {
    new ResourceCacheService(longPath);
} catch (IllegalArgumentException e) {
    // May fail on some filesystems (path too long)
}

Cache Directory Structure

The cache service organizes cached resources in a structured directory hierarchy:

{cache-root}/
├── {uuid-1}/
│   ├── model.onnx
│   └── tokenizer.json
├── {uuid-2}/
│   └── weights.bin
├── {uuid-3}/
│   └── config.json
└── {uuid-4}/
    ├── model_v2.onnx
    └── embeddings.dat

UUID Generation: Each unique resource URL path (excluding the filename) gets a UUID-based subdirectory to prevent naming conflicts.

// Example: How URLs map to cache structure

// URL 1: https://example.com/models/bert/model.onnx
// Path: {cache-root}/{uuid-for-models-bert}/model.onnx

// URL 2: https://example.com/models/bert/tokenizer.json
// Path: {cache-root}/{uuid-for-models-bert}/tokenizer.json
// (Same UUID as URL 1 - same path)

// URL 3: https://example.com/models/roberta/model.onnx
// Path: {cache-root}/{uuid-for-models-roberta}/model.onnx
// (Different UUID - different path)

// URL 4: https://other-site.com/models/bert/model.onnx
// Path: {cache-root}/{uuid-for-other-site}/model.onnx
// (Different UUID - different domain)

Filename Preservation: Original filenames are preserved within the UUID directories.

// URL: https://example.com/path/to/my-custom-model-v1.2.3.onnx
// Cached as: {cache-root}/{uuid}/my-custom-model-v1.2.3.onnx
// Original filename preserved

Fragment Support: URI fragments are appended to filenames with underscore separator.

// URL: https://example.com/model.onnx#section
// Cached as: {cache-root}/{uuid}/model.onnx_section

// URL: https://example.com/model.onnx#part-a
// Cached as: {cache-root}/{uuid}/model.onnx_part-a

Query Parameters: Query parameters are included in the cache key but not in the filename.

// URL 1: https://example.com/model.onnx?version=1.2
// Cached as: {cache-root}/{uuid1}/model.onnx

// URL 2: https://example.com/model.onnx?version=1.3
// Cached as: {cache-root}/{uuid2}/model.onnx
// Different UUID because query differs

Usage in TransformersEmbeddingModel

The ResourceCacheService is created automatically by TransformersEmbeddingModel during initialization:

// Internal usage (automatic)
TransformersEmbeddingModel model = new TransformersEmbeddingModel();
model.setResourceCacheDirectory("/custom/cache/dir");
model.setDisableCaching(false);
model.afterPropertiesSet();
// ResourceCacheService created and used internally

// Internal process:
// 1. Model checks if caching is enabled
// 2. Creates ResourceCacheService with configured directory
// 3. Uses service to cache model and tokenizer resources
// 4. Service persists across embedding operations

Configuration Integration:

TransformersEmbeddingModel model = new TransformersEmbeddingModel();

// Cache enabled (default)
model.setDisableCaching(false);
model.setResourceCacheDirectory("/var/cache/models");
// ResourceCacheService created with /var/cache/models

// Cache disabled
model.setDisableCaching(true);
// ResourceCacheService not created or not used

// Default cache location
model.setDisableCaching(false);
// No setResourceCacheDirectory call
// ResourceCacheService uses default: {java.io.tmpdir}/spring-ai-onnx-generative

Standalone Usage

The cache service can also be used independently for caching any Spring Resources:

import org.springframework.ai.transformers.ResourceCacheService;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;

public class ResourceCacheExample {

    public static void main(String[] args) throws Exception {
        // Create cache service
        ResourceCacheService cache = new ResourceCacheService(
            "/tmp/my-resource-cache"
        );

        // Cache various resources
        Resource model = cache.getCachedResource(
            "https://example.com/large-file.bin"
        );

        Resource config = cache.getCachedResource(
            "https://example.com/config.json"
        );

        Resource weights = cache.getCachedResource(
            "https://example.com/weights.dat"
        );

        // Access cached content
        byte[] modelBytes = model.getContentAsByteArray();
        System.out.println("Model size: " + modelBytes.length + " bytes");

        String configContent = new String(
            config.getContentAsByteArray(),
            StandardCharsets.UTF_8
        );
        System.out.println("Config: " + configContent);

        // Check cache size
        File cacheDir = new File("/tmp/my-resource-cache");
        long cacheSize = calculateDirectorySize(cacheDir);
        System.out.println("Cache size: " + (cacheSize / 1024 / 1024) + " MB");

        // Clear cache when done
        cache.deleteCacheFolder();
    }

    static long calculateDirectorySize(File dir) {
        long size = 0;
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    size += calculateDirectorySize(file);
                } else {
                    size += file.length();
                }
            }
        }
        return size;
    }
}

Custom Caching Strategy:

public class CustomCachingService {
    private final ResourceCacheService cache;
    private final Map<String, Long> downloadTimes;
    
    public CustomCachingService(String cacheDir) {
        this.cache = new ResourceCacheService(cacheDir);
        this.downloadTimes = new ConcurrentHashMap<>();
    }
    
    public Resource getCachedResourceWithMetrics(String uri) {
        long start = System.currentTimeMillis();
        Resource resource = cache.getCachedResource(uri);
        long elapsed = System.currentTimeMillis() - start;
        
        downloadTimes.put(uri, elapsed);
        
        if (elapsed > 100) {
            System.out.println("Slow access for " + uri + ": " + elapsed + "ms");
        }
        
        return resource;
    }
    
    public void printStatistics() {
        System.out.println("Download/Access Statistics:");
        downloadTimes.forEach((uri, time) -> {
            System.out.println(uri + ": " + time + "ms");
        });
    }
}

Caching Behavior Details

First Access

// Detailed flow for first access:
ResourceCacheService cache = new ResourceCacheService("/cache");
Resource resource = cache.getCachedResource("https://example.com/model.onnx");

// Internal steps:
// 1. Extract URI: "https://example.com/model.onnx"
// 2. Check schema: "https" - not in excluded list, proceed with caching
// 3. Generate UUID for path: "https://example.com/model" -> UUID
// 4. Construct cache path: /cache/{UUID}/model.onnx
// 5. Check if cached file exists: false (first access)
// 6. Open connection to https://example.com/model.onnx
// 7. Download file to cache path
// 8. Return FileUrlResource pointing to /cache/{UUID}/model.onnx

Subsequent Access

// Detailed flow for subsequent access:
ResourceCacheService cache = new ResourceCacheService("/cache");
Resource resource = cache.getCachedResource("https://example.com/model.onnx");

// Internal steps:
// 1. Extract URI: "https://example.com/model.onnx"
// 2. Check schema: "https" - not excluded, proceed with caching
// 3. Generate UUID for path: "https://example.com/model" -> UUID (same as before)
// 4. Construct cache path: /cache/{UUID}/model.onnx
// 5. Check if cached file exists: true (cached from first access)
// 6. Return FileUrlResource pointing to /cache/{UUID}/model.onnx
// No download occurs

Thread Safety

The cache service is not explicitly thread-safe. For concurrent access, external synchronization may be required.

// Potential race condition
ResourceCacheService cache = new ResourceCacheService();

// Thread 1 and Thread 2 simultaneously:
Resource r1 = cache.getCachedResource("https://example.com/model.onnx");
Resource r2 = cache.getCachedResource("https://example.com/model.onnx");

// Possible issues:
// - Both threads may try to download the same file
// - File may be partially written when another thread reads it
// - Cache directory may be accessed concurrently

// Solution: External synchronization
Object lock = new Object();
synchronized (lock) {
    Resource resource = cache.getCachedResource("https://example.com/model.onnx");
}

// Or use concurrent map to ensure only one download per URI
ConcurrentHashMap<String, Resource> resourceMap = new ConcurrentHashMap<>();
Resource resource = resourceMap.computeIfAbsent(
    "https://example.com/model.onnx",
    uri -> cache.getCachedResource(uri)
);

TransformersEmbeddingModel Usage: In practice, the TransformersEmbeddingModel initializes the cache once during afterPropertiesSet(), avoiding concurrent access issues.

// Safe usage in TransformersEmbeddingModel
TransformersEmbeddingModel model = new TransformersEmbeddingModel();
model.afterPropertiesSet();
// Caching happens during initialization (single-threaded)

// Subsequent embedding calls don't access cache
float[] embedding = model.embed("text"); // No cache access

Error Handling

The cache service throws the following exceptions:

IllegalArgumentException

// Null cache directory or excluded schemas list
try {
    new ResourceCacheService((String) null);
} catch (IllegalArgumentException e) {
    // Exception: cache directory must not be null
}

try {
    cache.setExcludedUriSchemas(null);
} catch (IllegalArgumentException e) {
    // Exception: excluded schemas must not be null
}

IllegalStateException

// Resource caching fails (wraps IO exceptions)
ResourceCacheService cache = new ResourceCacheService();
try {
    Resource cached = cache.getCachedResource("https://invalid-url.com/file.bin");
} catch (IllegalStateException e) {
    // Wrapped IOException from network error
    System.err.println("Caching failed: " + e.getMessage());
    
    // Get underlying cause
    Throwable cause = e.getCause();
    if (cause instanceof IOException) {
        // Handle IO error
    }
}

IOException

// May propagate from resource access operations
try {
    ResourceCacheService cache = new ResourceCacheService();
    Resource cached = cache.getCachedResource("https://example.com/model.onnx");
    
    // IOException when accessing cached resource
    InputStream is = cached.getInputStream();
    byte[] data = is.readAllBytes();
} catch (IOException e) {
    // File read error from cached resource
}

Error Handling Example:

public Resource getCachedResourceSafely(String uri) {
    ResourceCacheService cache = new ResourceCacheService();
    
    try {
        return cache.getCachedResource(uri);
    } catch (IllegalStateException e) {
        System.err.println("Caching failed: " + e.getMessage());
        
        // Determine error type
        Throwable cause = e.getCause();
        if (cause instanceof UnknownHostException) {
            System.err.println("Network error: host not found");
        } else if (cause instanceof FileNotFoundException) {
            System.err.println("Resource not found (404)");
        } else if (cause instanceof IOException) {
            System.err.println("I/O error: " + cause.getMessage());
        }
        
        // Fallback: use original resource without caching
        DefaultResourceLoader loader = new DefaultResourceLoader();
        return loader.getResource(uri);
    }
}

Performance Considerations

Disk Space

// Cached resources consume local disk space
// Model files can be large: 50MB - 5GB or more

// Monitor cache size
File cacheDir = new File("/var/cache/spring-ai");
long size = calculateDirectorySize(cacheDir);
System.out.println("Cache size: " + (size / 1024 / 1024) + " MB");

// Set up alerts for large cache
if (size > 10L * 1024 * 1024 * 1024) { // > 10GB
    System.err.println("WARNING: Cache exceeds 10GB");
    // Consider cleanup
}

Startup Time

// First run: Downloads and caches resources (slower)
long start = System.currentTimeMillis();
TransformersEmbeddingModel model1 = new TransformersEmbeddingModel();
model1.afterPropertiesSet();
long firstStartup = System.currentTimeMillis() - start;
System.out.println("First startup: " + firstStartup + "ms");
// Typical: 5000-30000ms (network dependent)

// Subsequent runs: Uses cached files (faster)
start = System.currentTimeMillis();
TransformersEmbeddingModel model2 = new TransformersEmbeddingModel();
model2.afterPropertiesSet();
long cachedStartup = System.currentTimeMillis() - start;
System.out.println("Cached startup: " + cachedStartup + "ms");
// Typical: 100-500ms

System.out.println("Speedup: " + (firstStartup / (double) cachedStartup) + "x");

Network

// Only first access requires network
ResourceCacheService cache = new ResourceCacheService();

// First access: network download
long start = System.currentTimeMillis();
Resource r1 = cache.getCachedResource("https://example.com/large-model.onnx");
long networkTime = System.currentTimeMillis() - start;
System.out.println("Network download: " + networkTime + "ms");

// Subsequent access: local filesystem (no network)
start = System.currentTimeMillis();
Resource r2 = cache.getCachedResource("https://example.com/large-model.onnx");
long localTime = System.currentTimeMillis() - start;
System.out.println("Local access: " + localTime + "ms");
// Typically <10ms

System.out.println("Speedup: " + (networkTime / (double) localTime) + "x");
// Typically 100-1000x faster

Cache Invalidation

// No automatic invalidation - cache persists indefinitely
// Manual invalidation required

// Option 1: Delete entire cache
cache.deleteCacheFolder();

// Option 2: Delete specific cached file
File cacheDir = new File("/var/cache/spring-ai");
// Find and delete specific cached file (requires knowing UUID)

// Option 3: Implement TTL-based cleanup
public class TTLCacheService {
    private final ResourceCacheService cache;
    private final Map<String, Long> cacheTimes;
    private final long ttlMillis;
    
    public TTLCacheService(String cacheDir, long ttlMillis) {
        this.cache = new ResourceCacheService(cacheDir);
        this.cacheTimes = new ConcurrentHashMap<>();
        this.ttlMillis = ttlMillis;
    }
    
    public Resource getCachedResource(String uri) {
        Long cacheTime = cacheTimes.get(uri);
        long now = System.currentTimeMillis();
        
        if (cacheTime != null && (now - cacheTime) > ttlMillis) {
            // TTL expired, force re-download
            cache.deleteCacheFolder();
            cacheTimes.clear();
        }
        
        Resource resource = cache.getCachedResource(uri);
        cacheTimes.putIfAbsent(uri, now);
        return resource;
    }
}

Best Practices

1. Custom Cache Location for Production

// ❌ BAD: Use default temp directory (may be cleared)
ResourceCacheService cache = new ResourceCacheService();

// ✅ GOOD: Use dedicated persistent directory
ResourceCacheService cache = new ResourceCacheService("/var/cache/spring-ai-models");

2. Cache Monitoring

// Monitor disk usage
@Scheduled(fixedRate = 3600000) // Every hour
public void monitorCache() {
    File cacheDir = new File("/var/cache/spring-ai");
    long size = calculateDirectorySize(cacheDir);
    long sizeMB = size / 1024 / 1024;
    
    System.out.println("Cache size: " + sizeMB + " MB");
    
    // Alert if too large
    if (sizeMB > 10000) { // > 10GB
        System.err.println("Cache exceeds 10GB, consider cleanup");
    }
}

3. Version Management

// Include version in resource URLs
String modelUri = "https://example.com/models/v1.2/model.onnx";
// Different versions cached separately

// Or use query parameters
String modelUri = "https://example.com/models/model.onnx?version=1.2";

4. Initialization Time Optimization

// Pre-warm cache during deployment (before application starts)
public class CacheWarmer {
    public static void main(String[] args) throws Exception {
        ResourceCacheService cache = new ResourceCacheService("/var/cache/spring-ai");
        
        // Download all required models
        cache.getCachedResource("https://example.com/models/model.onnx");
        cache.getCachedResource("https://example.com/tokenizers/tokenizer.json");
        
        System.out.println("Cache warmed successfully");
    }
}

// Run during deployment:
// $ java -cp app.jar CacheWarmer
// $ java -jar app.jar  # Fast startup (cache already populated)

5. Periodic Cache Cleanup

// Implement periodic cleanup for development/test environments
@Configuration
public class CacheConfig {
    
    @Bean
    public ResourceCacheService resourceCacheService() {
        return new ResourceCacheService("/var/cache/spring-ai");
    }
    
    @Scheduled(cron = "0 0 2 * * *") // 2 AM daily
    public void cleanupCache(ResourceCacheService cacheService) {
        if (isDevOrTestEnvironment()) {
            cacheService.deleteCacheFolder();
            System.out.println("Cache cleaned up");
        }
    }
    
    private boolean isDevOrTestEnvironment() {
        String env = System.getProperty("spring.profiles.active");
        return "dev".equals(env) || "test".equals(env);
    }
}

6. Error Handling and Fallback

public Resource getCachedResourceWithFallback(String uri) {
    ResourceCacheService cache = new ResourceCacheService();
    
    try {
        // Try to get cached resource
        return cache.getCachedResource(uri);
    } catch (IllegalStateException e) {
        System.err.println("Caching failed, using direct access: " + e.getMessage());
        
        // Fallback: disable caching and access directly
        DefaultResourceLoader loader = new DefaultResourceLoader();
        return loader.getResource(uri);
    }
}

7. Concurrent Access Protection

// Thread-safe cache access for concurrent initialization
public class ThreadSafeCacheService {
    private final ResourceCacheService cache;
    private final ConcurrentHashMap<String, Resource> resources;
    
    public ThreadSafeCacheService(String cacheDir) {
        this.cache = new ResourceCacheService(cacheDir);
        this.resources = new ConcurrentHashMap<>();
    }
    
    public Resource getCachedResource(String uri) {
        return resources.computeIfAbsent(uri, u -> cache.getCachedResource(u));
    }
}
tessl i tessl/maven-org-springframework-ai--spring-ai-transformers@1.1.1

docs

index.md

tile.json