A custom caching toolkit = a Java-backed cache (in-JVM, optionally distributed) wrapped by toolkit services so that every process app reads reference data with one call and never hits the source repeatedly. Design and a compact implementation:
// Java (managed jar): simple TTL cache per key, thread safe, per JVM (each cluster member has its own copy - fine for reference data)
public class RefCache {
private static final ConcurrentHashMap<String, Object[]> CACHE = new ConcurrentHashMap<>(); // key -> [expiresAtMillis, jsonValue]
public static String get(String key) { Object[] e = CACHE.get(key); if (e == null || (long) e[0] < System.currentTimeMillis()) { CACHE.remove(key); return null; } return (String) e[1]; }
public static void put(String key, String json, long ttlSeconds) { CACHE.put(key, new Object[] { System.currentTimeMillis() + ttlSeconds * 1000, json }); }
public static void evict(String prefix) { CACHE.keySet().removeIf(k -> k.startsWith(prefix)); }
public static String stats() { return CACHE.size() + " entries"; }
}// toolkit service flow "Get cached" - inputs key, ttlSeconds, loaderServiceName; output value (String JSON)
var v = RefCache.get(tw.local.key); // Java integration step or LiveConnect: Packages.com.example.RefCache.get(...)
if (v == null) {
var inputs = new tw.object.Map(); inputs.put("key", tw.local.key);
var result = tw.system.executeServiceByName(tw.local.loaderServiceName, inputs); // the loader knows how to fetch (SQL, REST) and returns JSON
v = result.get("value");
RefCache.put(tw.local.key, v, tw.local.ttlSeconds || 600);
}
tw.local.value = v;
// callers: tw.local.countries = JSON.parse( GetCached("ref:countries", 3600, "Load countries").value )Design points: keys with a namespace prefix so that evict("ref:") clears a domain after an admin change (expose an Evict service and a tiny admin dashboard); TTLs per key type; store JSON strings (immutable, serialisable) rather than business objects; on a cluster either accept per-member copies (reference data) or use a distributed cache (Redis via Jedis, Hazelcast, or the WebSphere DynaCache DistributedMap from JNDI on traditional) when consistency matters; never cache per-user or transactional data. Compare with the built-in service caching (question 2897): the OOB cache is keyed by service inputs and cleared only by redeploy - the custom toolkit gives you TTL, eviction, statistics and cross-app sharing. On CP4BA the same jar works; for cross-pod caching use Redis (an operator-managed one) rather than in-JVM maps.
References