killbill-memoizeit

Merge branch 'ehcache3'

3/22/2017 4:16:07 AM

Changes

jaxrs/pom.xml 4(+0 -4)

payment/pom.xml 4(+0 -4)

pom.xml 2(+1 -1)

Details

diff --git a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
index c9cc74a..ba32fd5 100644
--- a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
+++ b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -56,7 +56,7 @@ public class DefaultAccountInternalApi extends DefaultAccountApiBase implements 
 
     private final ImmutableAccountInternalApi immutableAccountInternalApi;
     private final AccountDao accountDao;
-    private final CacheController bcdCacheController;
+    private final CacheController<UUID, Integer> bcdCacheController;
 
     @Inject
     public DefaultAccountInternalApi(final ImmutableAccountInternalApi immutableAccountInternalApi,
@@ -106,7 +106,7 @@ public class DefaultAccountInternalApi extends DefaultAccountApiBase implements 
     @Override
     public int getBCD(final UUID accountId, final InternalTenantContext context) throws AccountApiException {
         final CacheLoaderArgument arg = createBCDCacheLoaderArgument(context);
-        final Integer result = (Integer) bcdCacheController.get(accountId, arg);
+        final Integer result = bcdCacheController.get(accountId, arg);
         return result != null ? result : DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL;
     }
 
@@ -160,8 +160,8 @@ public class DefaultAccountInternalApi extends DefaultAccountApiBase implements 
     private CacheLoaderArgument createBCDCacheLoaderArgument(final InternalTenantContext context) {
         final AccountBCDCacheLoader.LoaderCallback loaderCallback = new AccountBCDCacheLoader.LoaderCallback() {
             @Override
-            public Object loadAccountBCD(final UUID accountId, final InternalTenantContext context) {
-                Object result = accountDao.getAccountBCD(accountId, context);
+            public Integer loadAccountBCD(final UUID accountId, final InternalTenantContext context) {
+                Integer result = accountDao.getAccountBCD(accountId, context);
                 if (result != null) {
                     // If the value is 0, then account BCD was not set so we don't want to create a cache entry
                     result = result.equals(DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL) ? null : result;
diff --git a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultImmutableAccountInternalApi.java b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultImmutableAccountInternalApi.java
index 250d47a..4bdf0c6 100644
--- a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultImmutableAccountInternalApi.java
+++ b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultImmutableAccountInternalApi.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2016-2017 Groupon, Inc
+ * Copyright 2016-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -48,8 +48,8 @@ public class DefaultImmutableAccountInternalApi implements ImmutableAccountInter
 
     private final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
     private final NonEntityDao nonEntityDao;
-    private final CacheControllerDispatcher cacheControllerDispatcher;
-    private final CacheController accountCacheController;
+    private final CacheController<Long, ImmutableAccountData> accountCacheController;
+    private final CacheController<String, Long> recordIdCacheController;
 
     @Inject
     public DefaultImmutableAccountInternalApi(final IDBI dbi,
@@ -59,26 +59,26 @@ public class DefaultImmutableAccountInternalApi implements ImmutableAccountInter
         // This API will directly issue queries instead of relying on the DAO (introduced to avoid Guice circular dependencies with InternalCallContextFactory)
         this.transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao, null);
         this.nonEntityDao = nonEntityDao;
-        this.cacheControllerDispatcher = cacheControllerDispatcher;
         this.accountCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE);
+        this.recordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
     }
 
     @Override
     public ImmutableAccountData getImmutableAccountDataById(final UUID accountId, final InternalTenantContext context) throws AccountApiException {
-        final Long recordId = nonEntityDao.retrieveRecordIdFromObject(accountId, ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID));
+        final Long recordId = nonEntityDao.retrieveRecordIdFromObject(accountId, ObjectType.ACCOUNT, recordIdCacheController);
         return getImmutableAccountDataByRecordId(recordId, context);
     }
 
     @Override
     public ImmutableAccountData getImmutableAccountDataByRecordId(final Long recordId, final InternalTenantContext context) throws AccountApiException {
         final CacheLoaderArgument arg = createImmutableAccountCacheLoaderArgument(context);
-        return (ImmutableAccountData) accountCacheController.get(recordId, arg);
+        return accountCacheController.get(recordId, arg);
     }
 
     private CacheLoaderArgument createImmutableAccountCacheLoaderArgument(final InternalTenantContext context) {
         final LoaderCallback loaderCallback = new LoaderCallback() {
             @Override
-            public Object loadAccount(final Long recordId, final InternalTenantContext context) {
+            public ImmutableAccountData loadAccount(final Long recordId, final InternalTenantContext context) {
                 final Account account = getAccountByRecordIdInternal(recordId, context);
                 return account != null ? new DefaultImmutableAccountData(account) : null;
             }
diff --git a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java
index 7792ab4..a77c9e7 100644
--- a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java
+++ b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -25,6 +25,7 @@ import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.DefaultAccount;
 import org.killbill.billing.account.api.DefaultImmutableAccountData;
+import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.account.dao.AccountDao;
 import org.killbill.billing.account.dao.AccountModelDao;
 import org.killbill.billing.callcontext.InternalTenantContext;
@@ -36,8 +37,8 @@ import org.killbill.billing.util.dao.NonEntityDao;
 public class DefaultAccountApiBase {
 
     private final AccountDao accountDao;
-    private final CacheControllerDispatcher cacheControllerDispatcher;
-    private final CacheController accountCacheController;
+    private final CacheController<Long, ImmutableAccountData> accountCacheController;
+    private final CacheController<String, Long> recordIdCacheController;
     private final NonEntityDao nonEntityDao;
 
     public DefaultAccountApiBase(final AccountDao accountDao,
@@ -45,17 +46,17 @@ public class DefaultAccountApiBase {
                                  final CacheControllerDispatcher cacheControllerDispatcher) {
         this.accountDao = accountDao;
         this.nonEntityDao = nonEntityDao;
-        this.cacheControllerDispatcher = cacheControllerDispatcher;
         this.accountCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE);
+        this.recordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
     }
 
     protected Account getAccountById(final UUID accountId, final InternalTenantContext context) throws AccountApiException {
-        final Long recordId = nonEntityDao.retrieveRecordIdFromObject(accountId, ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID));
+        final Long recordId = nonEntityDao.retrieveRecordIdFromObject(accountId, ObjectType.ACCOUNT, recordIdCacheController);
         final Account account = getAccountByRecordIdInternal(recordId, context);
         if (account == null) {
             throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, accountId);
         }
-        accountCacheController.putIfAbsent(accountId, new DefaultImmutableAccountData(account));
+        accountCacheController.putIfAbsent(recordId, new DefaultImmutableAccountData(account));
         return account;
     }
 
@@ -65,7 +66,8 @@ public class DefaultAccountApiBase {
             throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, key);
         }
         final Account account = new DefaultAccount(accountModelDao);
-        accountCacheController.putIfAbsent(account.getId(), new DefaultImmutableAccountData(account));
+        final Long recordId = nonEntityDao.retrieveRecordIdFromObject(account.getId(), ObjectType.ACCOUNT, recordIdCacheController);
+        accountCacheController.putIfAbsent(recordId, new DefaultImmutableAccountData(account));
         return account;
     }
 
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/SubscriptionChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/SubscriptionChecker.java
index 1b0c6c9..d46e1d5 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/SubscriptionChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/SubscriptionChecker.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -22,6 +24,7 @@ import java.util.UUID;
 import javax.inject.Inject;
 
 import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,14 +49,14 @@ public class SubscriptionChecker {
     private final SubscriptionBaseInternalApi subscriptionApi;
     private final AuditChecker auditChecker;
     private final NonEntityDao nonEntityDao;
-    private final CacheControllerDispatcher cacheControllerDispatcher;
+    private final CacheController<String, UUID> objectIdCacheController;
 
     @Inject
     public SubscriptionChecker(final SubscriptionBaseInternalApi subscriptionApi, final AuditChecker auditChecker, final NonEntityDao nonEntityDao, final CacheControllerDispatcher cacheControllerDispatcher) {
         this.subscriptionApi = subscriptionApi;
         this.auditChecker = auditChecker;
         this.nonEntityDao = nonEntityDao;
-        this.cacheControllerDispatcher = cacheControllerDispatcher;
+        objectIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
     }
 
     public SubscriptionBaseBundle checkBundleNoAudits(final UUID bundleId, final UUID expectedAccountId, final String expectedKey, final InternalTenantContext context) throws SubscriptionBaseApiException {
@@ -65,7 +68,7 @@ public class SubscriptionChecker {
     }
 
     public SubscriptionBase checkSubscriptionCreated(final UUID subscriptionId, final InternalCallContext context) throws SubscriptionBaseApiException {
-        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, objectIdCacheController);
         final CallContext callContext = context.toCallContext(tenantId);
 
         final SubscriptionBase subscription = subscriptionApi.getSubscriptionFromId(subscriptionId, context);
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
index ee21422..918058b 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -27,6 +27,7 @@ import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.StandaloneCatalog;
 import org.killbill.billing.catalog.StandaloneCatalogWithPriceOverride;
 import org.killbill.billing.catalog.VersionedCatalog;
+import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.io.VersionedCatalogLoader;
 import org.killbill.billing.catalog.override.PriceOverride;
@@ -52,7 +53,7 @@ public class EhCacheCatalogCache implements CatalogCache {
 
     private final Logger logger = LoggerFactory.getLogger(EhCacheCatalogCache.class);
 
-    private final CacheController cacheController;
+    private final CacheController<Long, Catalog> cacheController;
     private final VersionedCatalogLoader loader;
     private final CacheLoaderArgument cacheLoaderArgumentWithTemplateFiltering;
     private final CacheLoaderArgument cacheLoaderArgument;
@@ -113,7 +114,7 @@ public class EhCacheCatalogCache implements CatalogCache {
                     final StandaloneCatalogWithPriceOverride curWithOverride = new StandaloneCatalogWithPriceOverride(cur, priceOverride, tenantContext.getTenantRecordId(), internalCallContextFactory);
                     tenantCatalog.add(curWithOverride);
                 }
-                cacheController.add(tenantContext.getTenantRecordId(), tenantCatalog);
+                cacheController.putIfAbsent(tenantContext.getTenantRecordId(), tenantCatalog);
             }
             return tenantCatalog;
         } catch (final IllegalStateException e) {
@@ -150,7 +151,7 @@ public class EhCacheCatalogCache implements CatalogCache {
     private CacheLoaderArgument initializeCacheLoaderArgument(final boolean filterTemplateCatalog) {
         final LoaderCallback loaderCallback = new LoaderCallback() {
             @Override
-            public Object loadCatalog(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException {
+            public Catalog loadCatalog(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException {
                 return loader.load(catalogXMLs, filterTemplateCatalog, tenantRecordId);
             }
         };
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheOverriddenPlanCache.java b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheOverriddenPlanCache.java
index f0b3f7c..b8f0a58 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheOverriddenPlanCache.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheOverriddenPlanCache.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -48,7 +48,7 @@ import com.google.common.collect.Iterables;
 
 public class EhCacheOverriddenPlanCache implements OverriddenPlanCache {
 
-    private final CacheController cacheController;
+    private final CacheController<String, Plan> cacheController;
     private final LoaderCallback loaderCallback;
     private final CatalogOverrideDao overrideDao;
 
@@ -58,7 +58,7 @@ public class EhCacheOverriddenPlanCache implements OverriddenPlanCache {
         this.cacheController = cacheControllerDispatcher.getCacheController(CacheType.OVERRIDDEN_PLAN);
         this.loaderCallback = new LoaderCallback() {
             @Override
-            public Object loadPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException {
+            public Plan loadPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException {
                 return loadOverriddenPlan(planName, catalog, context);
             }
         };
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java
index db2577e..164a9b8 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -36,7 +36,6 @@ import org.killbill.billing.ObjectType;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.entitlement.DefaultEntitlementService;
-import org.killbill.billing.entitlement.EntitlementService;
 import org.killbill.billing.entitlement.api.BlockingApiException;
 import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
@@ -46,6 +45,7 @@ import org.killbill.billing.entitlement.block.BlockingChecker.BlockingAggregator
 import org.killbill.billing.entitlement.block.StatelessBlockingChecker;
 import org.killbill.billing.entitlement.engine.core.BlockingTransitionNotificationKey;
 import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.dao.NonEntityDao;
@@ -107,7 +107,7 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
     private final Clock clock;
     private final NotificationQueueService notificationQueueService;
     private final PersistentBus eventBus;
-    private final CacheControllerDispatcher cacheControllerDispatcher;
+    private final CacheController<String, UUID> objectIdCacheController;
     private final NonEntityDao nonEntityDao;
 
     private final StatelessBlockingChecker statelessBlockingChecker = new StatelessBlockingChecker();
@@ -118,7 +118,7 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
         this.clock = clock;
         this.notificationQueueService = notificationQueueService;
         this.eventBus = eventBus;
-        this.cacheControllerDispatcher = cacheControllerDispatcher;
+        this.objectIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
         this.nonEntityDao = nonEntityDao;
     }
 
@@ -260,12 +260,12 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
         final List<BlockingState> bundleBlockingStates;
         final List<BlockingState> subscriptionBlockingStates;
         if (type == BlockingStateType.SUBSCRIPTION) {
-            final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID), handle);
+            final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, objectIdCacheController, handle);
             accountBlockingStates = getBlockingState(sqlDao, accountId, BlockingStateType.ACCOUNT, upToDate, context);
             bundleBlockingStates = getBlockingState(sqlDao, bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, upToDate, context);
             subscriptionBlockingStates = getBlockingState(sqlDao, blockableId, BlockingStateType.SUBSCRIPTION, upToDate, context);
         } else if (type == BlockingStateType.SUBSCRIPTION_BUNDLE) {
-            final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID), handle);
+            final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, objectIdCacheController, handle);
             accountBlockingStates = getBlockingState(sqlDao, accountId, BlockingStateType.ACCOUNT, upToDate, context);
             bundleBlockingStates = getBlockingState(sqlDao, blockableId, BlockingStateType.SUBSCRIPTION_BUNDLE, upToDate, context);
             subscriptionBlockingStates = ImmutableList.<BlockingState>of();
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index 4976f49..b9743f6 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -59,6 +59,7 @@ import org.killbill.billing.invoice.notification.ParentInvoiceCommitmentPoster;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.config.definition.InvoiceConfig;
@@ -115,7 +116,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     private final CBADao cbaDao;
     private final InvoiceConfig invoiceConfig;
     private final Clock clock;
-    private final CacheControllerDispatcher cacheControllerDispatcher;
+    private final CacheController<String, UUID> objectIdCacheController;
     private final NonEntityDao nonEntityDao;
     private final ParentInvoiceCommitmentPoster parentInvoiceCommitmentPoster;
     private final TagInternalApi tagInternalApi;
@@ -142,7 +143,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         this.invoiceDaoHelper = invoiceDaoHelper;
         this.cbaDao = cbaDao;
         this.clock = clock;
-        this.cacheControllerDispatcher = cacheControllerDispatcher;
+        this.objectIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
         this.nonEntityDao = nonEntityDao;
         this.parentInvoiceCommitmentPoster = parentInvoiceCommitmentPoster;
     }
@@ -832,7 +833,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 }
 
                 if (completion) {
-                    final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID), entitySqlDaoWrapperFactory.getHandle());
+                    final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, objectIdCacheController, entitySqlDaoWrapperFactory.getHandle());
                     notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, invoicePayment, accountId, context.getUserToken(), context);
                 }
                 return null;

jaxrs/pom.xml 4(+0 -4)

diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index b76c36c..fd16d11 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -76,10 +76,6 @@
             <artifactId>joda-time</artifactId>
         </dependency>
         <dependency>
-            <groupId>net.sf.ehcache</groupId>
-            <artifactId>ehcache</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.apache.shiro</groupId>
             <artifactId>shiro-core</artifactId>
         </dependency>
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
index 27168ba..e9af063 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
@@ -47,6 +47,8 @@ import org.joda.time.DateTimeZone;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.account.api.ImmutableAccountData;
+import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceUserApi;
 import org.killbill.billing.jaxrs.json.AdminPaymentJson;
@@ -67,8 +69,11 @@ import org.killbill.billing.util.api.CustomFieldUserApi;
 import org.killbill.billing.util.api.RecordIdApi;
 import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.billing.util.config.tenant.PerTenantConfig;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.tag.Tag;
 import org.killbill.billing.util.tag.dao.SystemTags;
@@ -82,6 +87,7 @@ import org.killbill.notificationq.api.NotificationQueue;
 import org.killbill.notificationq.api.NotificationQueueService;
 
 import com.fasterxml.jackson.core.JsonGenerator;
+import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
@@ -92,8 +98,6 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
-import net.sf.ehcache.CacheManager;
-import net.sf.ehcache.Ehcache;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
@@ -107,7 +111,7 @@ public class AdminResource extends JaxRsResourceBase {
     private final AdminPaymentApi adminPaymentApi;
     private final InvoiceUserApi invoiceUserApi;
     private final TenantUserApi tenantApi;
-    private final CacheManager cacheManager;
+    private final CacheControllerDispatcher cacheControllerDispatcher;
     private final RecordIdApi recordIdApi;
     private final PersistentBus persistentBus;
     private final NotificationQueueService notificationQueueService;
@@ -121,7 +125,7 @@ public class AdminResource extends JaxRsResourceBase {
                          final PaymentApi paymentApi,
                          final AdminPaymentApi adminPaymentApi,
                          final InvoiceUserApi invoiceUserApi,
-                         final CacheManager cacheManager,
+                         final CacheControllerDispatcher cacheControllerDispatcher,
                          final TenantUserApi tenantApi,
                          final RecordIdApi recordIdApi,
                          final PersistentBus persistentBus,
@@ -133,7 +137,7 @@ public class AdminResource extends JaxRsResourceBase {
         this.invoiceUserApi = invoiceUserApi;
         this.tenantApi = tenantApi;
         this.recordIdApi = recordIdApi;
-        this.cacheManager = cacheManager;
+        this.cacheControllerDispatcher = cacheControllerDispatcher;
         this.persistentBus = persistentBus;
         this.notificationQueueService = notificationQueueService;
     }
@@ -322,17 +326,17 @@ public class AdminResource extends JaxRsResourceBase {
     public Response invalidatesCache(@QueryParam("cacheName") final String cacheName,
                                      @javax.ws.rs.core.Context final HttpServletRequest request) {
         if (null != cacheName && !cacheName.isEmpty()) {
-            final Ehcache cache = cacheManager.getEhcache(cacheName);
-            // check if cache is null
-            if (cache == null) {
+            // Clear given cache
+            final CacheType cacheType = CacheType.findByName(cacheName);
+            if (cacheType != null) {
+                cacheControllerDispatcher.getCacheController(cacheType).removeAll();
+            } else {
                 log.warn("Cache for specified cacheName='{}' does not exist or is not alive", cacheName);
                 return Response.status(Status.BAD_REQUEST).build();
             }
-            // Clear given cache
-            cache.removeAll();
         } else {
             // if not given a specific cacheName, clear all
-            cacheManager.clearAll();
+            cacheControllerDispatcher.clearAll();
         }
         return Response.status(Status.OK).build();
     }
@@ -342,20 +346,23 @@ public class AdminResource extends JaxRsResourceBase {
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Invalidates Caches per account level")
     @ApiResponses(value = {})
-    public Response invalidatesCacheByAccount(@PathParam("accountId") final String accountId,
+    public Response invalidatesCacheByAccount(@PathParam("accountId") final String accountIdStr,
                                               @javax.ws.rs.core.Context final HttpServletRequest request) {
+        final TenantContext tenantContext = context.createContext(request);
+        final UUID accountId = UUID.fromString(accountIdStr);
+        final Long accountRecordId = recordIdApi.getRecordId(accountId, ObjectType.ACCOUNT, tenantContext);
 
-        // clear account-record-id cache by accountId
-        final Ehcache accountRecordIdCache = cacheManager.getEhcache(CacheType.ACCOUNT_RECORD_ID.getCacheName());
-        accountRecordIdCache.remove(accountId);
+        // clear account-record-id cache by accountId (note: String!)
+        final CacheController<String, Long> accountRecordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID);
+        accountRecordIdCacheController.remove(accountIdStr);
 
-        // clear account-immutable cache by accountId
-        final Ehcache accountImmutableCache = cacheManager.getEhcache(CacheType.ACCOUNT_IMMUTABLE.getCacheName());
-        accountImmutableCache.remove(UUID.fromString(accountId));
+        // clear account-immutable cache by account record id
+        final CacheController<Long, ImmutableAccountData> accountImmutableCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE);
+        accountImmutableCacheController.remove(accountRecordId);
 
-        // clear account-bcd cache by accountId
-        final Ehcache accountBcdCache = cacheManager.getEhcache(CacheType.ACCOUNT_BCD.getCacheName());
-        accountBcdCache.remove(UUID.fromString(accountId));
+        // clear account-bcd cache by accountId (note: UUID!)
+        final CacheController<UUID, Integer> accountBCDCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_BCD);
+        accountBCDCacheController.remove(accountId);
 
         return Response.status(Status.OK).build();
     }
@@ -369,52 +376,51 @@ public class AdminResource extends JaxRsResourceBase {
                                              @javax.ws.rs.core.Context final HttpServletRequest request) throws TenantApiException {
 
         // creating Tenant Context from Request
-        TenantContext tenantContext = context.createContext(request);
+        final TenantContext tenantContext = context.createContext(request);
 
-        Tenant currentTenant = tenantApi.getTenantById(tenantContext.getTenantId());
+        final Tenant currentTenant = tenantApi.getTenantById(tenantContext.getTenantId());
 
         // getting Tenant Record Id
-        Long tenantRecordId = recordIdApi.getRecordId(tenantContext.getTenantId(), ObjectType.TENANT, tenantContext);
+        final Long tenantRecordId = recordIdApi.getRecordId(tenantContext.getTenantId(), ObjectType.TENANT, tenantContext);
+
+        final Function<String, Boolean> tenantKeysMatcher = new Function<String, Boolean>() {
+            @Override
+            public Boolean apply(@Nullable final String key) {
+                return key != null && key.endsWith("::" + tenantRecordId);
+            }
+        };
 
         // clear tenant-record-id cache by tenantId
-        final Ehcache tenantRecordIdCache = cacheManager.getEhcache(CacheType.TENANT_RECORD_ID.getCacheName());
-        tenantRecordIdCache.remove(currentTenant.getId().toString());
+        final CacheController<String, Long> tenantRecordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID);
+        tenantRecordIdCacheController.remove(currentTenant.getId().toString());
 
         // clear tenant-payment-state-machine-config cache by tenantRecordId
-        final Ehcache tenantPaymentStateMachineConfigCache = cacheManager.getEhcache(CacheType.TENANT_PAYMENT_STATE_MACHINE_CONFIG.getCacheName());
-        removeCacheByKey(tenantPaymentStateMachineConfigCache, tenantRecordId.toString());
+        final CacheController<String, Object> tenantPaymentStateMachineConfigCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_PAYMENT_STATE_MACHINE_CONFIG);
+        tenantPaymentStateMachineConfigCacheController.remove(tenantKeysMatcher);
 
         // clear tenant cache by tenantApiKey
-        final Ehcache tenantCache = cacheManager.getEhcache(CacheType.TENANT.getCacheName());
-        tenantCache.remove(currentTenant.getApiKey());
+        final CacheController<String, Tenant> tenantCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT);
+        tenantCacheController.remove(currentTenant.getApiKey());
 
         // clear tenant-kv cache by tenantRecordId
-        final Ehcache tenantKvCache = cacheManager.getEhcache(CacheType.TENANT_KV.getCacheName());
-        removeCacheByKey(tenantKvCache, tenantRecordId.toString());
+        final CacheController<String, String> tenantKVCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_KV);
+        tenantKVCacheController.remove(tenantKeysMatcher);
 
         // clear tenant-config cache by tenantRecordId
-        final Ehcache tenantConfigCache = cacheManager.getEhcache(CacheType.TENANT_CONFIG.getCacheName());
-        tenantConfigCache.remove(tenantRecordId);
+        final CacheController<Long, PerTenantConfig> tenantConfigCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_CONFIG);
+        tenantConfigCacheController.remove(tenantRecordId);
 
         // clear tenant-overdue-config cache by tenantRecordId
-        final Ehcache tenantOverdueConfigCache = cacheManager.getEhcache(CacheType.TENANT_OVERDUE_CONFIG.getCacheName());
-        tenantOverdueConfigCache.remove(tenantRecordId);
+        final CacheController<Long, Object> tenantOverdueConfigCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_OVERDUE_CONFIG);
+        tenantOverdueConfigCacheController.remove(tenantRecordId);
 
         // clear tenant-catalog cache by tenantRecordId
-        final Ehcache tenantCatalogCache = cacheManager.getEhcache(CacheType.TENANT_CATALOG.getCacheName());
-        tenantCatalogCache.remove(tenantRecordId);
+        final CacheController<Long, Catalog> tenantCatalogCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_CATALOG);
+        tenantCatalogCacheController.remove(tenantRecordId);
 
         return Response.status(Status.OK).build();
     }
 
-    private void removeCacheByKey(final Ehcache tenantCache, final String tenantRecordId) {
-        for (Object key : tenantCache.getKeys()) {
-            if (null != key && key.toString().endsWith("::" + tenantRecordId)) {
-                tenantCache.remove(key);
-            }
-        }
-    }
-
     private Iterable<NotificationEventWithMetadata<NotificationEvent>> getNotifications(@Nullable final String queueName,
                                                                                         @Nullable final String serviceName,
                                                                                         final boolean includeInProcessing,
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java b/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java
index 7285e87..d3363e1 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -44,7 +44,7 @@ public class EhCacheOverdueConfigCache implements OverdueConfigCache {
 
     private static final Logger log = LoggerFactory.getLogger(EhCacheOverdueConfigCache.class);
 
-    private final CacheController cacheController;
+    private final CacheController<Long, OverdueConfig> cacheController;
     private final CacheLoaderArgument cacheLoaderArgument;
 
     private OverdueConfig defaultOverdueConfig;
@@ -97,7 +97,7 @@ public class EhCacheOverdueConfigCache implements OverdueConfigCache {
         // The cache loader might choke on some bad xml -- unlikely since we check its validity prior storing it,
         // but to be on the safe side;;
         try {
-            final OverdueConfig overdueConfig = (OverdueConfig) cacheController.get(tenantContext.getTenantRecordId(), cacheLoaderArgument);
+            final OverdueConfig overdueConfig = cacheController.get(tenantContext.getTenantRecordId(), cacheLoaderArgument);
             return (overdueConfig != null) ? overdueConfig : defaultOverdueConfig;
         } catch (final IllegalStateException e) {
             throw new OverdueApiException(ErrorCode.OVERDUE_INVALID_FOR_TENANT, tenantContext.getTenantRecordId());
@@ -114,7 +114,7 @@ public class EhCacheOverdueConfigCache implements OverdueConfigCache {
     private CacheLoaderArgument initializeCacheLoaderArgument() {
         final LoaderCallback loaderCallback = new LoaderCallback() {
             @Override
-            public Object loadOverdueConfig(final String overdueConfigXML) throws OverdueApiException {
+            public OverdueConfig loadOverdueConfig(final String overdueConfigXML) throws OverdueApiException {
                 final InputStream overdueConfigStream = new ByteArrayInputStream(overdueConfigXML.getBytes());
                 final URI uri;
                 try {
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
index 43f00c1..8bed766 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -45,6 +45,7 @@ import org.killbill.billing.overdue.notification.OverdueAsyncBusNotificationKey.
 import org.killbill.billing.overdue.notification.OverdueAsyncBusNotifier;
 import org.killbill.billing.overdue.notification.OverduePoster;
 import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.CallOrigin;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
@@ -65,7 +66,7 @@ public class OverdueListener {
     private static final Logger log = LoggerFactory.getLogger(OverdueListener.class);
 
     private final InternalCallContextFactory internalCallContextFactory;
-    private final CacheControllerDispatcher cacheControllerDispatcher;
+    private final CacheController<String, UUID> objectIdCacheController;
     private final Clock clock;
     private final OverduePoster asyncPoster;
     private final OverdueConfigCache overdueConfigCache;
@@ -84,7 +85,7 @@ public class OverdueListener {
         this.clock = clock;
         this.asyncPoster = asyncPoster;
         this.overdueConfigCache = overdueConfigCache;
-        this.cacheControllerDispatcher = cacheControllerDispatcher;
+        this.objectIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
         this.internalCallContextFactory = internalCallContextFactory;
         this.accountApi = accountApi;
     }
@@ -96,7 +97,7 @@ public class OverdueListener {
             final InternalCallContext internalCallContext = createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
             insertBusEventIntoNotificationQueue(event.getObjectId(), OverdueAsyncBusNotificationAction.CLEAR, internalCallContext);
         } else if (event.getTagDefinition().getName().equals(ControlTagType.WRITTEN_OFF.toString()) && event.getObjectType() == ObjectType.INVOICE) {
-            final UUID accountId = nonEntityDao.retrieveIdFromObject(event.getSearchKey1(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+            final UUID accountId = nonEntityDao.retrieveIdFromObject(event.getSearchKey1(), ObjectType.ACCOUNT, objectIdCacheController);
             insertBusEventIntoNotificationQueue(accountId, event);
         }
     }
@@ -107,7 +108,7 @@ public class OverdueListener {
         if (event.getTagDefinition().getName().equals(ControlTagType.OVERDUE_ENFORCEMENT_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
             insertBusEventIntoNotificationQueue(event.getObjectId(), event);
         } else if (event.getTagDefinition().getName().equals(ControlTagType.WRITTEN_OFF.toString()) && event.getObjectType() == ObjectType.INVOICE) {
-            final UUID accountId = nonEntityDao.retrieveIdFromObject(event.getSearchKey1(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+            final UUID accountId = nonEntityDao.retrieveIdFromObject(event.getSearchKey1(), ObjectType.ACCOUNT, objectIdCacheController);
             insertBusEventIntoNotificationQueue(accountId, event);
         }
     }

payment/pom.xml 4(+0 -4)

diff --git a/payment/pom.xml b/payment/pom.xml
index 0fc7d88..3f3e881 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -97,10 +97,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>net.sf.ehcache</groupId>
-            <artifactId>ehcache</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.apache.shiro</groupId>
             <artifactId>shiro-core</artifactId>
         </dependency>
diff --git a/payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java b/payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java
index ae76cb2..2d1c3dc 100644
--- a/payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java
+++ b/payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2016-2017 Groupon, Inc
+ * Copyright 2016-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -51,7 +51,7 @@ public class EhCacheStateMachineConfigCache implements StateMachineConfigCache {
     private static final Logger logger = LoggerFactory.getLogger(EhCacheStateMachineConfigCache.class);
 
     private final TenantInternalApi tenantInternalApi;
-    private final CacheController cacheController;
+    private final CacheController<String, StateMachineConfig> cacheController;
     private final CacheInvalidationCallback cacheInvalidationCallback;
     private final LoaderCallback loaderCallback;
 
@@ -105,7 +105,7 @@ public class EhCacheStateMachineConfigCache implements StateMachineConfigCache {
             // It means we are using the default state machine config in a multi-tenant deployment
             if (pluginPaymentStateMachineConfig == null) {
                 pluginPaymentStateMachineConfig = defaultPaymentStateMachineConfig;
-                cacheController.add(pluginConfigKey, pluginPaymentStateMachineConfig);
+                cacheController.putIfAbsent(pluginConfigKey, pluginPaymentStateMachineConfig);
             }
             return pluginPaymentStateMachineConfig;
         } catch (final IllegalStateException e) {
diff --git a/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCache.java b/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCache.java
index 9960e7a..cb2383a 100644
--- a/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCache.java
+++ b/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCache.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2016-2017 Groupon, Inc
+ * Copyright 2016-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -38,7 +38,6 @@ import org.testng.annotations.Test;
 
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Resources;
-import net.sf.ehcache.CacheException;
 
 public class TestStateMachineConfigCache extends PaymentTestSuiteNoDB {
 
@@ -82,7 +81,7 @@ public class TestStateMachineConfigCache extends PaymentTestSuiteNoDB {
             @Override
             public String answer(final InvocationOnMock invocation) throws Throwable {
                 if (shouldThrow.get()) {
-                    throw new RuntimeException();
+                    throw new RuntimeException("For test purposes");
                 }
                 final InternalTenantContext internalContext = (InternalTenantContext) invocation.getArguments()[1];
                 if (multiTenantRecordId.equals(internalContext.getTenantRecordId())) {
@@ -141,8 +140,9 @@ public class TestStateMachineConfigCache extends PaymentTestSuiteNoDB {
         try {
             stateMachineConfigCache.getPaymentStateMachineConfig(pluginName, multiTenantContext);
             Assert.fail();
-        } catch (final CacheException exception) {
+        } catch (final RuntimeException exception) {
             Assert.assertTrue(exception.getCause() instanceof RuntimeException);
+            Assert.assertEquals(exception.getCause().getMessage(), "For test purposes");
         }
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCacheInvalidationCallback.java b/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCacheInvalidationCallback.java
index 9ead7cd..18b1f31 100644
--- a/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCacheInvalidationCallback.java
+++ b/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCacheInvalidationCallback.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2016-2017 Groupon, Inc
+ * Copyright 2016-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -32,8 +32,6 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import net.sf.ehcache.CacheException;
-
 public class TestStateMachineConfigCacheInvalidationCallback extends PaymentTestSuiteNoDB {
 
     private InternalTenantContext multiTenantContext;
@@ -67,7 +65,7 @@ public class TestStateMachineConfigCacheInvalidationCallback extends PaymentTest
             @Override
             public String answer(final InvocationOnMock invocation) throws Throwable {
                 if (shouldThrow.get()) {
-                    throw new RuntimeException();
+                    throw new RuntimeException("For test purposes");
                 }
                 return null;
             }
@@ -87,8 +85,9 @@ public class TestStateMachineConfigCacheInvalidationCallback extends PaymentTest
         try {
             stateMachineConfigCache.getPaymentStateMachineConfig(pluginName, multiTenantContext);
             Assert.fail();
-        } catch (final CacheException exception) {
+        } catch (final RuntimeException exception) {
             Assert.assertTrue(exception.getCause() instanceof RuntimeException);
+            Assert.assertEquals(exception.getCause().getMessage(), "For test purposes");
         }
 
         // No exception (cached)

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index e867bc5..061c2b8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.140.13</version>
+        <version>0.140.17</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.18.5-SNAPSHOT</version>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java
index 23325ad..c00915a 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java
@@ -16,11 +16,13 @@
 
 package org.killbill.billing.jaxrs;
 
-import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
+import org.killbill.automaton.StateMachineConfig;
+import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.PriceListSet;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.client.RequestOptions;
@@ -29,12 +31,14 @@ import org.killbill.billing.client.model.PaymentMethod;
 import org.killbill.billing.client.model.PaymentMethodPluginDetail;
 import org.killbill.billing.client.model.Subscription;
 import org.killbill.billing.client.model.Tenant;
+import org.killbill.billing.overdue.api.OverdueConfig;
 import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
+import org.killbill.billing.util.config.tenant.PerTenantConfig;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.common.io.Resources;
-import net.sf.ehcache.Ehcache;
 
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
@@ -45,31 +49,31 @@ public class TestCache extends TestJaxrsBase {
     @Test(groups = "slow", description = "Can Invalidate (clear) a Cache by name")
     public void testInvalidateCacheByName() throws Exception {
         // get Ehcache item with name "record-id"
-        final Ehcache cache = cacheManager.getEhcache(CacheType.RECORD_ID.getCacheName());
+        final CacheController<String, Long> cache = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
         // verify that it is not null and has one stored key (the default tenant created for all integration tests)
         assertNotNull(cache);
-        Assert.assertEquals(cache.getSize(), 1);
+        Assert.assertEquals(cache.size(), 1);
 
         // invalidate the specified cache
-        killBillClient.invalidateCacheByName(cache.getName(), requestOptions);
+        killBillClient.invalidateCacheByName(CacheType.RECORD_ID.getCacheName(), requestOptions);
 
         // verify that now the cache is empty and has no keys stored
-        Assert.assertEquals(cache.getSize(), 0);
+        Assert.assertEquals(cache.size(), 0);
     }
 
     @Test(groups = "slow", description = "Can Invalidate (clear) all available Caches")
     public void testInvalidateAllCaches() throws Exception {
         // get Ehcache item with name "record-id"
-        final Ehcache cache = cacheManager.getEhcache(CacheType.RECORD_ID.getCacheName());
+        final CacheController<String, Long> cache = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
         // verify that it is not null and has one stored key (the default tenant created for all integration tests)
         assertNotNull(cache);
-        Assert.assertEquals(cache.getSize(), 1);
+        Assert.assertEquals(cache.size(), 1);
 
         // invalidate all caches
         killBillClient.invalidateAllCaches(requestOptions);
 
         // verify that now the cache is empty and has no keys stored
-        Assert.assertEquals(cache.getSize(), 0);
+        Assert.assertEquals(cache.size(), 0);
     }
 
     @Test(groups = "slow", description = "Can Invalidate (clear) all Account Caches by accountId")
@@ -77,25 +81,23 @@ public class TestCache extends TestJaxrsBase {
         final Account input = createAccountNoPMBundleAndSubscription();
 
         // get all caches per account level
-        final Ehcache accountRecordIdCache = cacheManager.getEhcache(CacheType.ACCOUNT_RECORD_ID.getCacheName());
-        final Ehcache accountImmutableCache = cacheManager.getEhcache(CacheType.ACCOUNT_IMMUTABLE.getCacheName());
-        final Ehcache accountBcdCache = cacheManager.getEhcache(CacheType.ACCOUNT_BCD.getCacheName());
+        final CacheController<String, Long> accountRecordIdCache = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID);
+        final CacheController<Long, ImmutableAccountData> accountImmutableCache = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE);
+        final CacheController<UUID, Integer> accountBcdCache = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_BCD);
 
         // verify that they are not null and have the accountId stored as a key (the account created before)
-        assertNotNull(accountRecordIdCache);
-        assertNotNull(accountRecordIdCache.get(input.getAccountId().toString()));
-        assertNotNull(accountImmutableCache);
-        assertNotNull(accountImmutableCache.get(input.getAccountId()));
-        assertNotNull(accountBcdCache);
-        assertNotNull(accountBcdCache.get(input.getAccountId()));
+        assertTrue(accountRecordIdCache.isKeyInCache(input.getAccountId().toString()));
+        final Long accountRecordId = accountRecordIdCache.get(input.getAccountId().toString(), null);
+        assertTrue(accountImmutableCache.isKeyInCache(accountRecordId));
+        assertTrue(accountBcdCache.isKeyInCache(input.getAccountId()));
 
         // invalidate caches per account level by accountId
         killBillClient.invalidateCacheByAccount(input.getAccountId().toString(), requestOptions);
 
         // verify that now the caches don't have the accountId key stored
-        Assert.assertNull(accountRecordIdCache.get(input.getAccountId().toString()));
-        Assert.assertNull(accountImmutableCache.get(input.getAccountId()));
-        Assert.assertNull(accountBcdCache.get(input.getAccountId()));
+        Assert.assertFalse(accountRecordIdCache.isKeyInCache(input.getAccountId().toString()));
+        Assert.assertFalse(accountImmutableCache.isKeyInCache(accountRecordId));
+        Assert.assertFalse(accountBcdCache.isKeyInCache(input.getAccountId()));
     }
 
     @Test(groups = "slow", description = "Can Invalidate (clear) all Tenant Caches for current Tenant")
@@ -125,48 +127,40 @@ public class TestCache extends TestJaxrsBase {
         createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoiceWithInputOptions(inputOptions);
 
         // get all caches per tenant level
-        final Ehcache tenantRecordIdCache = cacheManager.getEhcache(CacheType.TENANT_RECORD_ID.getCacheName());
-        final Ehcache tenantPaymentStateMachineConfigCache = cacheManager.getEhcache(CacheType.TENANT_PAYMENT_STATE_MACHINE_CONFIG.getCacheName());
-        final Ehcache tenantCache = cacheManager.getEhcache(CacheType.TENANT.getCacheName());
-        final Ehcache tenantKvCache = cacheManager.getEhcache(CacheType.TENANT_KV.getCacheName());
-        final Ehcache tenantConfigCache = cacheManager.getEhcache(CacheType.TENANT_CONFIG.getCacheName());
-        final Ehcache tenantOverdueConfigCache = cacheManager.getEhcache(CacheType.TENANT_OVERDUE_CONFIG.getCacheName());
-        final Ehcache tenantCatalogCache = cacheManager.getEhcache(CacheType.TENANT_CATALOG.getCacheName());
-
-        // getting current Tenant's record Id from the specific Cache
-        Long tenantRecordId = (Long) tenantRecordIdCache.get(currentTenant.getTenantId().toString()).getObjectValue();
+        final CacheController<String, Long> tenantRecordIdCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID);
+        final CacheController<String, StateMachineConfig> tenantPaymentStateMachineConfigCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT_PAYMENT_STATE_MACHINE_CONFIG);
+        final CacheController<String, org.killbill.billing.tenant.api.Tenant> tenantCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT);
+        final CacheController<String, String> tenantKvCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT_KV);
+        final CacheController<Long, PerTenantConfig> tenantConfigCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT_CONFIG);
+        final CacheController<Long, OverdueConfig> tenantOverdueConfigCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT_OVERDUE_CONFIG);
+        final CacheController<Long, Catalog> tenantCatalogCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT_CATALOG);
 
         // verify that they are not null and have the expected tenant information
-        assertNotNull(tenantRecordIdCache);
-        assertNotNull(tenantRecordIdCache.get(currentTenant.getTenantId().toString()));
-        assertNotNull(tenantPaymentStateMachineConfigCache);
+        assertTrue(tenantRecordIdCache.isKeyInCache(currentTenant.getTenantId().toString()));
+        final Long tenantRecordId = tenantRecordIdCache.get(currentTenant.getTenantId().toString(), null);
+
         assertTrue(hasKeysByTenantRecordId(tenantPaymentStateMachineConfigCache, tenantRecordId.toString()));
-        assertNotNull(tenantCache);
-        assertNotNull(tenantCache.get(testApiKey));
-        assertNotNull(tenantKvCache);
+        assertTrue(tenantCache.isKeyInCache(testApiKey));
         assertTrue(hasKeysByTenantRecordId(tenantKvCache, tenantRecordId.toString()));
-        assertNotNull(tenantConfigCache);
-        assertNotNull(tenantConfigCache.get(tenantRecordId));
-        assertNotNull(tenantOverdueConfigCache);
-        assertNotNull(tenantOverdueConfigCache.get(tenantRecordId));
-        assertNotNull(tenantCatalogCache);
-        assertNotNull(tenantCatalogCache.get(tenantRecordId));
+        assertTrue(tenantConfigCache.isKeyInCache(tenantRecordId));
+        assertTrue(tenantOverdueConfigCache.isKeyInCache(tenantRecordId));
+        assertTrue(tenantCatalogCache.isKeyInCache(tenantRecordId));
 
         // invalidate caches per tenant level
         killBillClient.invalidateCacheByTenant(inputOptions);
 
         // verify that now the caches don't have the previous values
-        Assert.assertNull(tenantRecordIdCache.get(currentTenant.getTenantId().toString()));
+        assertFalse(tenantRecordIdCache.isKeyInCache(currentTenant.getTenantId().toString()));
         assertFalse(hasKeysByTenantRecordId(tenantPaymentStateMachineConfigCache, tenantRecordId.toString()));
-        Assert.assertNull(tenantCache.get(testApiKey));
+        assertFalse(tenantCache.isKeyInCache(testApiKey));
         assertFalse(hasKeysByTenantRecordId(tenantKvCache, tenantRecordId.toString()));
-        Assert.assertNull(tenantConfigCache.get(tenantRecordId));
-        Assert.assertNull(tenantOverdueConfigCache.get(tenantRecordId));
-        Assert.assertNull(tenantCatalogCache.get(tenantRecordId));
+        assertFalse(tenantConfigCache.isKeyInCache(tenantRecordId));
+        assertFalse(tenantOverdueConfigCache.isKeyInCache(tenantRecordId));
+        assertFalse(tenantCatalogCache.isKeyInCache(tenantRecordId));
     }
 
-    private boolean hasKeysByTenantRecordId(final Ehcache tenantCache, final String tenantRecordId) {
-        for (String key : (List<String>) tenantCache.getKeys()) {
+    private boolean hasKeysByTenantRecordId(final CacheController<String, ?> tenantCache, final String tenantRecordId) {
+        for (final String key : tenantCache.getKeys()) {
             if (key.endsWith("::" + tenantRecordId)) {
                 return true;
             }
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
index d4ec7bf..66007ef 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -74,7 +74,6 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.inject.Module;
 import com.google.inject.util.Modules;
-import net.sf.ehcache.CacheManager;
 
 public class TestJaxrsBase extends KillbillClient {
 
@@ -107,9 +106,6 @@ public class TestJaxrsBase extends KillbillClient {
     @Inject
     protected TenantCacheInvalidation tenantCacheInvalidation;
 
-    @Inject
-    protected CacheManager cacheManager;
-
     protected DaoConfig daoConfig;
     protected KillbillServerConfig serverConfig;
 
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidationCallback.java b/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidationCallback.java
index f07122f..ffc662c 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidationCallback.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidationCallback.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -32,7 +32,7 @@ public class TenantCacheInvalidationCallback implements CacheInvalidationCallbac
 
     private final Logger log = LoggerFactory.getLogger(TenantCacheInvalidationCallback.class);
 
-    private final CacheController<Object, Object> tenantKVCache;
+    private final CacheController<String, String> tenantKVCache;
 
     @Inject
     public TenantCacheInvalidationCallback(final CacheControllerDispatcher cacheControllerDispatcher) {
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/user/DefaultTenantUserApi.java b/tenant/src/main/java/org/killbill/billing/tenant/api/user/DefaultTenantUserApi.java
index b9a22bc..eb3f5ba 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/api/user/DefaultTenantUserApi.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/api/user/DefaultTenantUserApi.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -71,8 +71,8 @@ public class DefaultTenantUserApi implements TenantUserApi {
 
     private final TenantDao tenantDao;
     private final InternalCallContextFactory internalCallContextFactory;
-    private final CacheController<Object, Object> tenantKVCache;
-    private final CacheController<Object, Object> tenantCache;
+    private final CacheController<String, String> tenantKVCache;
+    private final CacheController<String, Tenant> tenantCache;
 
 
     @Inject
@@ -101,7 +101,7 @@ public class DefaultTenantUserApi implements TenantUserApi {
 
     @Override
     public Tenant getTenantByApiKey(final String key) throws TenantApiException {
-        final Tenant tenant = (Tenant) tenantCache.get(key, new CacheLoaderArgument(ObjectType.TENANT));
+        final Tenant tenant = tenantCache.get(key, new CacheLoaderArgument(ObjectType.TENANT));
         if (tenant == null) {
             throw new TenantApiException(ErrorCode.TENANT_DOES_NOT_EXIST_FOR_API_KEY, key);
         }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java
index d88ea69..6e9955d 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -22,7 +22,7 @@ import java.util.UUID;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 
-public class AccountBCDCacheLoader extends BaseCacheLoader {
+public class AccountBCDCacheLoader extends BaseCacheLoader<UUID, Integer> {
 
     @Override
     public CacheType getCacheType() {
@@ -30,30 +30,18 @@ public class AccountBCDCacheLoader extends BaseCacheLoader {
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof UUID)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
-        }
-
-        final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
-
+    public Integer compute(final UUID key, final CacheLoaderArgument cacheLoaderArgument) {
         if (cacheLoaderArgument.getArgs() == null ||
             !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
             throw new IllegalArgumentException("Missing LoaderCallback from the arguments ");
         }
 
         final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
-        return callback.loadAccountBCD((UUID) key, cacheLoaderArgument.getInternalTenantContext());
+        return callback.loadAccountBCD(key, cacheLoaderArgument.getInternalTenantContext());
     }
 
     public interface LoaderCallback {
-        Object loadAccountBCD(final UUID accountId, final InternalTenantContext context);
+
+        Integer loadAccountBCD(final UUID accountId, final InternalTenantContext context);
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/AccountRecordIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/AccountRecordIdCacheLoader.java
index 352a2d7..7cb8757 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/AccountRecordIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/AccountRecordIdCacheLoader.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -28,10 +28,8 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.skife.jdbi.v2.Handle;
 
-import net.sf.ehcache.loader.CacheLoader;
-
 @Singleton
-public class AccountRecordIdCacheLoader extends BaseIdCacheLoader implements CacheLoader {
+public class AccountRecordIdCacheLoader extends BaseIdCacheLoader<Long> {
 
     private final NonEntityDao nonEntityDao;
 
@@ -47,7 +45,7 @@ public class AccountRecordIdCacheLoader extends BaseIdCacheLoader implements Cac
     }
 
     @Override
-    protected Object doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
+    protected Long doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
         return nonEntityDao.retrieveAccountRecordIdFromObjectInTransaction(UUID.fromString(rawKey), objectType, null, handle);
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/AuditLogCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/AuditLogCacheLoader.java
index bc0761d..fac8514 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/AuditLogCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/AuditLogCacheLoader.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,25 +18,24 @@
 
 package org.killbill.billing.util.cache;
 
+import java.util.List;
+
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
-import org.skife.jdbi.v2.IDBI;
-
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.audit.dao.AuditLogModelDao;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.dao.AuditSqlDao;
-import org.killbill.billing.util.dao.NonEntityDao;
-
-import net.sf.ehcache.loader.CacheLoader;
+import org.skife.jdbi.v2.IDBI;
 
 @Singleton
-public class AuditLogCacheLoader extends BaseCacheLoader implements CacheLoader {
+public class AuditLogCacheLoader extends BaseCacheLoader<String, List<AuditLogModelDao>> {
 
     private final AuditSqlDao auditSqlDao;
 
     @Inject
-    public AuditLogCacheLoader(final IDBI dbi, final NonEntityDao nonEntityDao) {
+    public AuditLogCacheLoader(final IDBI dbi) {
         super();
         this.auditSqlDao = dbi.onDemand(AuditSqlDao.class);
     }
@@ -45,17 +46,8 @@ public class AuditLogCacheLoader extends BaseCacheLoader implements CacheLoader 
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
-        }
-
-        final Object[] args = ((CacheLoaderArgument) argument).getArgs();
+    public List<AuditLogModelDao> compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
+        final Object[] args = cacheLoaderArgument.getArgs();
         final String tableName = (String) args[0];
         final Long targetRecordId = (Long) args[1];
         final InternalTenantContext internalTenantContext = (InternalTenantContext) args[2];
diff --git a/util/src/main/java/org/killbill/billing/util/cache/AuditLogViaHistoryCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/AuditLogViaHistoryCacheLoader.java
index b819148..26d215a 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/AuditLogViaHistoryCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/AuditLogViaHistoryCacheLoader.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,25 +18,24 @@
 
 package org.killbill.billing.util.cache;
 
+import java.util.List;
+
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
-import org.skife.jdbi.v2.IDBI;
-
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.audit.dao.AuditLogModelDao;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.dao.AuditSqlDao;
-import org.killbill.billing.util.dao.NonEntityDao;
-
-import net.sf.ehcache.loader.CacheLoader;
+import org.skife.jdbi.v2.IDBI;
 
 @Singleton
-public class AuditLogViaHistoryCacheLoader extends BaseCacheLoader implements CacheLoader {
+public class AuditLogViaHistoryCacheLoader extends BaseCacheLoader<String, List<AuditLogModelDao>> {
 
     private final AuditSqlDao auditSqlDao;
 
     @Inject
-    public AuditLogViaHistoryCacheLoader(final IDBI dbi, final NonEntityDao nonEntityDao) {
+    public AuditLogViaHistoryCacheLoader(final IDBI dbi) {
         super();
         this.auditSqlDao = dbi.onDemand(AuditSqlDao.class);
     }
@@ -45,17 +46,8 @@ public class AuditLogViaHistoryCacheLoader extends BaseCacheLoader implements Ca
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
-        }
-
-        final Object[] args = ((CacheLoaderArgument) argument).getArgs();
+    public List<AuditLogModelDao> compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
+        final Object[] args = cacheLoaderArgument.getArgs();
         final String tableName = (String) args[0];
         final String historyTableName = (String) args[1];
         final Long targetRecordId = (Long) args[2];
diff --git a/util/src/main/java/org/killbill/billing/util/cache/BaseCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/BaseCacheLoader.java
index c0044c6..973f9f0 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/BaseCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/BaseCacheLoader.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -18,77 +18,13 @@
 
 package org.killbill.billing.util.cache;
 
-import java.util.Collection;
-import java.util.Map;
-
-import javax.inject.Inject;
-
 import org.killbill.billing.util.cache.Cachable.CacheType;
 
-import net.sf.ehcache.CacheException;
-import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.Status;
-import net.sf.ehcache.loader.CacheLoader;
-
-public abstract class BaseCacheLoader implements CacheLoader {
+public abstract class BaseCacheLoader<K, V> {
 
     static final String EMPTY_VALUE_PLACEHOLDER = "__#VALEUR!__";
 
-    private Status cacheLoaderStatus;
-
-    @Inject
-    public BaseCacheLoader() {
-        this.cacheLoaderStatus = Status.STATUS_UNINITIALISED;
-    }
-
     public abstract CacheType getCacheType();
 
-    @Override
-    public abstract Object load(final Object key, final Object argument);
-
-    @Override
-    public Object load(final Object key) throws CacheException {
-        throw new IllegalStateException("Method load is not implemented ");
-    }
-
-    @Override
-    public Map loadAll(final Collection keys) {
-        throw new IllegalStateException("Method loadAll is not implemented ");
-    }
-
-    @Override
-    public Map loadAll(final Collection keys, final Object argument) {
-        throw new IllegalStateException("Method loadAll is not implemented ");
-    }
-
-    @Override
-    public String getName() {
-        return this.getClass().getName();
-    }
-
-    @Override
-    public CacheLoader clone(final Ehcache cache) throws CloneNotSupportedException {
-        throw new IllegalStateException("Method clone is not implemented ");
-    }
-
-    @Override
-    public void init() {
-        this.cacheLoaderStatus = Status.STATUS_ALIVE;
-    }
-
-    @Override
-    public void dispose() throws CacheException {
-        cacheLoaderStatus = Status.STATUS_SHUTDOWN;
-    }
-
-    @Override
-    public Status getStatus() {
-        return cacheLoaderStatus;
-    }
-
-    protected void checkCacheLoaderStatus() {
-        if (getStatus() != Status.STATUS_ALIVE) {
-            throw new CacheException("CacheLoader is not available!");
-        }
-    }
+    public abstract V compute(final K key, final CacheLoaderArgument cacheLoaderArgument);
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/BaseIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/BaseIdCacheLoader.java
index c0329ae..e2cb993 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/BaseIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/BaseIdCacheLoader.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -18,40 +18,28 @@
 package org.killbill.billing.util.cache;
 
 import org.killbill.billing.ObjectType;
-import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.skife.jdbi.v2.Handle;
 
-public abstract class BaseIdCacheLoader extends BaseCacheLoader {
+public abstract class BaseIdCacheLoader<V> extends BaseCacheLoader<String, V> {
 
     protected BaseIdCacheLoader() {
         super();
     }
 
-    @Override
-    public abstract CacheType getCacheType();
-
-    protected abstract Object doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle);
+    protected abstract V doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle);
 
     @Override
-    public Object load(final Object key, final Object argument) {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
-        }
-
+    public V compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
         final String rawKey;
         if (getCacheType().isKeyPrefixedWithTableName()) {
-            final String[] parts = ((String) key).split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
+            final String[] parts = key.split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
             rawKey = parts[1];
         } else {
-            rawKey = (String) key;
+            rawKey = key;
         }
-        final ObjectType objectType = ((CacheLoaderArgument) argument).getObjectType();
-        final Handle handle = ((CacheLoaderArgument) argument).getHandle();
+
+        final ObjectType objectType = cacheLoaderArgument.getObjectType();
+        final Handle handle = cacheLoaderArgument.getHandle();
         return doRetrieveOperation(rawKey, objectType, handle);
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
index a70556c..eb90621 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -20,6 +22,14 @@ import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.List;
+import java.util.UUID;
+
+import org.killbill.billing.account.api.ImmutableAccountData;
+import org.killbill.billing.catalog.api.Catalog;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.tenant.api.Tenant;
+import org.killbill.billing.util.config.tenant.PerTenantConfig;
 
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD})
@@ -45,56 +55,60 @@ public @interface Cachable {
 
     enum CacheType {
 
-        /* Mapping from object 'id (UUID)' -> object 'recordId (Long' */
-        RECORD_ID(RECORD_ID_CACHE_NAME, false),
+        /* Mapping from object 'id (UUID as String)' -> object 'recordId (Long)' */
+        RECORD_ID(RECORD_ID_CACHE_NAME, String.class, Long.class, false),
 
-        /* Mapping from object 'id (UUID)' -> matching account object 'accountRecordId (Long)' */
-        ACCOUNT_RECORD_ID(ACCOUNT_RECORD_ID_CACHE_NAME, false),
+        /* Mapping from object 'id (UUID as String)' -> matching account object 'accountRecordId (Long)' */
+        ACCOUNT_RECORD_ID(ACCOUNT_RECORD_ID_CACHE_NAME, String.class, Long.class, false),
 
-        /* Mapping from object 'id (UUID)' -> matching object 'tenantRecordId (Long)' */
-        TENANT_RECORD_ID(TENANT_RECORD_ID_CACHE_NAME, false),
+        /* Mapping from object 'id (UUID as String)' -> matching object 'tenantRecordId (Long)' */
+        TENANT_RECORD_ID(TENANT_RECORD_ID_CACHE_NAME, String.class, Long.class, false),
 
-        /* Mapping from object 'recordId (Long') -> object 'id (UUID)'  */
-        OBJECT_ID(OBJECT_ID_CACHE_NAME, true),
+        /* Mapping from object 'recordId (Long as String)' -> object 'id (UUID)'  */
+        OBJECT_ID(OBJECT_ID_CACHE_NAME, String.class, UUID.class, true),
 
         /* Mapping from object 'tableName::targetRecordId' -> matching objects 'Iterable<AuditLog>' */
-        AUDIT_LOG(AUDIT_LOG_CACHE_NAME, true),
+        AUDIT_LOG(AUDIT_LOG_CACHE_NAME, String.class, List.class, true),
 
         /* Mapping from object 'tableName::historyTableName::targetRecordId' -> matching objects 'Iterable<AuditLog>' */
-        AUDIT_LOG_VIA_HISTORY(AUDIT_LOG_VIA_HISTORY_CACHE_NAME, true),
+        AUDIT_LOG_VIA_HISTORY(AUDIT_LOG_VIA_HISTORY_CACHE_NAME, String.class, List.class, true),
 
         /* Tenant catalog cache */
-        TENANT_CATALOG(TENANT_CATALOG_CACHE_NAME, false),
+        TENANT_CATALOG(TENANT_CATALOG_CACHE_NAME, Long.class, Catalog.class, false),
 
         /* Tenant payment state machine config cache */
-        TENANT_PAYMENT_STATE_MACHINE_CONFIG(TENANT_PAYMENT_STATE_MACHINE_CONFIG_CACHE_NAME, false),
+        TENANT_PAYMENT_STATE_MACHINE_CONFIG(TENANT_PAYMENT_STATE_MACHINE_CONFIG_CACHE_NAME, String.class, Object.class, false),
 
         /* Tenant overdue config cache */
-        TENANT_OVERDUE_CONFIG(TENANT_OVERDUE_CONFIG_CACHE_NAME, false),
+        TENANT_OVERDUE_CONFIG(TENANT_OVERDUE_CONFIG_CACHE_NAME, Long.class, Object.class, false),
 
         /* Tenant overdue config cache */
-        TENANT_CONFIG(TENANT_CONFIG_CACHE_NAME, false),
+        TENANT_CONFIG(TENANT_CONFIG_CACHE_NAME, Long.class, PerTenantConfig.class, false),
 
         /* Tenant config cache */
-        TENANT_KV(TENANT_KV_CACHE_NAME, false),
+        TENANT_KV(TENANT_KV_CACHE_NAME, String.class, String.class, false),
 
         /* Tenant config cache */
-        TENANT(TENANT_CACHE_NAME, false),
+        TENANT(TENANT_CACHE_NAME, String.class, Tenant.class, false),
 
         /* Overwritten plans  */
-        OVERRIDDEN_PLAN(OVERRIDDEN_PLAN_CACHE_NAME, false),
+        OVERRIDDEN_PLAN(OVERRIDDEN_PLAN_CACHE_NAME, String.class, Plan.class, false),
 
         /* Immutable account data config cache */
-        ACCOUNT_IMMUTABLE(ACCOUNT_IMMUTABLE_CACHE_NAME, false),
+        ACCOUNT_IMMUTABLE(ACCOUNT_IMMUTABLE_CACHE_NAME, Long.class, ImmutableAccountData.class, false),
 
         /* Account BCD config cache */
-        ACCOUNT_BCD(ACCOUNT_BCD_CACHE_NAME, false);
+        ACCOUNT_BCD(ACCOUNT_BCD_CACHE_NAME, UUID.class, Integer.class, false);
 
         private final String cacheName;
+        private final Class keyType;
+        private final Class valueType;
         private final boolean isKeyPrefixedWithTableName;
 
-        CacheType(final String cacheName, final boolean isKeyPrefixedWithTableName) {
+        CacheType(final String cacheName, final Class keyType, final Class valueType, final boolean isKeyPrefixedWithTableName) {
             this.cacheName = cacheName;
+            this.keyType = keyType;
+            this.valueType = valueType;
             this.isKeyPrefixedWithTableName = isKeyPrefixedWithTableName;
         }
 
@@ -102,6 +116,14 @@ public @interface Cachable {
             return cacheName;
         }
 
+        public Class<?> getKeyType() {
+            return keyType;
+        }
+
+        public Class<?> getValueType() {
+            return valueType;
+        }
+
         public boolean isKeyPrefixedWithTableName() { return isKeyPrefixedWithTableName; }
 
         public static CacheType findByName(final String input) {
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheController.java b/util/src/main/java/org/killbill/billing/util/cache/CacheController.java
index 2c6be1e..46ee5dc 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheController.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheController.java
@@ -16,18 +16,24 @@
 
 package org.killbill.billing.util.cache;
 
+import java.util.List;
+
 import org.killbill.billing.util.cache.Cachable.CacheType;
 
+import com.google.common.base.Function;
+
 public interface CacheController<K, V> {
 
-    void add(K key, V value);
+    List<K> getKeys();
 
-    V get(K key, CacheLoaderArgument objectType);
+    boolean isKeyInCache(K key);
 
-    V get(K key);
+    V get(K key, CacheLoaderArgument objectType);
 
     boolean remove(K key);
 
+    void remove(Function<K, Boolean> keyMatcher);
+
     void putIfAbsent(final K key, V value);
 
     int size();
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcher.java b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcher.java
index bdf1302..2d9b966 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcher.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcher.java
@@ -44,8 +44,8 @@ public class CacheControllerDispatcher {
         caches = new HashMap<CacheType, CacheController<Object, Object>>();
     }
 
-    public CacheController<Object, Object> getCacheController(final CacheType cacheType) {
-        return caches.get(cacheType);
+    public <K, V> CacheController<K, V> getCacheController(final CacheType cacheType) {
+        return cast(caches.get(cacheType));
     }
 
     public void clearAll() {
@@ -53,4 +53,9 @@ public class CacheControllerDispatcher {
             cacheController.removeAll();
         }
     }
+
+    @SuppressWarnings("unchecked")
+    private static <K, V> CacheController<K, V> cast(final CacheController<?, ?> cache) {
+        return (CacheController<K, V>) cache;
+    }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
index f980ad7..302176c 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -18,9 +18,9 @@
 
 package org.killbill.billing.util.cache;
 
-import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Set;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
@@ -29,12 +29,8 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableList;
 import net.sf.ehcache.CacheManager;
 import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.loader.CacheLoader;
 
 // Build the abstraction layer between EhCache and Kill Bill
 public class CacheControllerDispatcherProvider implements Provider<CacheControllerDispatcher> {
@@ -42,40 +38,31 @@ public class CacheControllerDispatcherProvider implements Provider<CacheControll
     private static final Logger logger = LoggerFactory.getLogger(CacheControllerDispatcherProvider.class);
 
     private final CacheManager cacheManager;
+    private final Set<BaseCacheLoader> cacheLoaders;
 
     @Inject
-    public CacheControllerDispatcherProvider(final CacheManager cacheManager) {
+    public CacheControllerDispatcherProvider(final CacheManager cacheManager,
+                                             final Set<BaseCacheLoader> cacheLoaders) {
         this.cacheManager = cacheManager;
+        this.cacheLoaders = cacheLoaders;
     }
 
     @Override
     public CacheControllerDispatcher get() {
         final Map<CacheType, CacheController<Object, Object>> cacheControllers = new LinkedHashMap<CacheType, CacheController<Object, Object>>();
-        for (final String cacheName : cacheManager.getCacheNames()) {
-            final CacheType cacheType = CacheType.findByName(cacheName);
+        for (final BaseCacheLoader cacheLoader : cacheLoaders) {
+            final CacheType cacheType = cacheLoader.getCacheType();
 
-            final Collection<EhCacheBasedCacheController<Object, Object>> cacheControllersForCacheName = getCacheControllersForCacheName(cacheName, cacheType);
-            // EhCache supports multiple cache loaders per type, but not Kill Bill - take the first one
-            if (cacheControllersForCacheName.size() > 0) {
-                final EhCacheBasedCacheController<Object, Object> ehCacheBasedCacheController = cacheControllersForCacheName.iterator().next();
-                cacheControllers.put(cacheType, ehCacheBasedCacheController);
+            final Ehcache cache = cacheManager.getEhcache(cacheType.getCacheName());
+            if (cache == null) {
+                logger.warn("Cache for cacheName='{}' not configured - check your ehcache.xml", cacheLoader.getCacheType().getCacheName());
+                continue;
             }
-        }
-        return new CacheControllerDispatcher(cacheControllers);
-    }
 
-    private Collection<EhCacheBasedCacheController<Object, Object>> getCacheControllersForCacheName(final String name, final CacheType cacheType) {
-        final Ehcache cache = cacheManager.getEhcache(name);
-        if (cache == null) {
-            logger.warn("No cache configured for name {}", name);
-            return ImmutableList.<EhCacheBasedCacheController<Object, Object>>of();
+            final CacheController<Object, Object> ehCacheBasedCacheController = new EhCacheBasedCacheController<Object, Object>(cache, cacheLoader);
+            cacheControllers.put(cacheType, ehCacheBasedCacheController);
         }
-        // The CacheLoaders were registered in EhCacheCacheManagerProvider
-        return Collections2.transform(cache.getRegisteredCacheLoaders(), new Function<CacheLoader, EhCacheBasedCacheController<Object, Object>>() {
-            @Override
-            public EhCacheBasedCacheController<Object, Object> apply(final CacheLoader input) {
-                return new EhCacheBasedCacheController<Object, Object>(cache, cacheType);
-            }
-        });
+
+        return new CacheControllerDispatcher(cacheControllers);
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheLoaderArgument.java b/util/src/main/java/org/killbill/billing/util/cache/CacheLoaderArgument.java
index f559c0f..1724c59 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheLoaderArgument.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheLoaderArgument.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -18,6 +18,8 @@
 
 package org.killbill.billing.util.cache;
 
+import java.util.Arrays;
+
 import javax.annotation.Nullable;
 
 import org.killbill.billing.ObjectType;
@@ -61,4 +63,14 @@ public class CacheLoaderArgument {
     public Handle getHandle() {
         return handle;
     }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("CacheLoaderArgument{");
+        sb.append("objectType=").append(objectType);
+        sb.append(", args=").append(Arrays.toString(args));
+        sb.append(", internalTenantContext=").append(internalTenantContext);
+        sb.append('}');
+        return sb.toString();
+    }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
index 08d04d2..6372e3b 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2012 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -18,51 +18,93 @@
 
 package org.killbill.billing.util.cache;
 
-import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
 
 import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Function;
 import net.sf.ehcache.Ehcache;
 import net.sf.ehcache.Element;
 
 public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> {
 
+    private static final Logger logger = LoggerFactory.getLogger(EhCacheBasedCacheController.class);
+
     private final Ehcache cache;
-    private final CacheType cacheType;
+    private final BaseCacheLoader<K, V> baseCacheLoader;
 
-    public EhCacheBasedCacheController(final Ehcache cache, final CacheType cacheType) {
+    public EhCacheBasedCacheController(final Ehcache cache, final BaseCacheLoader<K, V> baseCacheLoader) {
         this.cache = cache;
-        this.cacheType = cacheType;
+        this.baseCacheLoader = baseCacheLoader;
     }
 
     @Override
-    public void add(final K key, final V value) {
-        cache.putIfAbsent(new Element(key, value));
+    public List<K> getKeys() {
+        return cache.getKeys();
     }
 
     @Override
-    public V get(final K key, @Nullable final CacheLoaderArgument cacheLoaderArgument) {
-        return getWithOrWithoutCacheLoaderArgument(key, cacheLoaderArgument);
+    public boolean isKeyInCache(final K key) {
+        return cache.isKeyInCache(key);
     }
 
     @Override
-    public V get(final K key) {
-        return getWithOrWithoutCacheLoaderArgument(key, null);
+    public V get(final K key, final CacheLoaderArgument cacheLoaderArgument) {
+        checkKey(key);
+
+        final V value;
+        if (!isKeyInCache(key)) {
+            value = computeAndCacheValue(key, cacheLoaderArgument);
+        } else {
+            final Element element = cache.get(key);
+            if (element == null) {
+                value = null;
+            } else if (element.isExpired()) {
+                value = computeAndCacheValue(key, cacheLoaderArgument);
+            } else {
+                value = (V) element.getObjectValue();
+            }
+        }
+
+        if (value == null || value.equals(BaseCacheLoader.EMPTY_VALUE_PLACEHOLDER)) {
+            return null;
+        } else {
+            checkValue(value);
+            return value;
+        }
     }
 
-    public void putIfAbsent(final K key, V value) {
-        final Element element = new Element(key, value);
-        cache.putIfAbsent(element);
+    @Override
+    public void putIfAbsent(final K key, final V value) {
+        checkKey(key);
+        checkValue(value);
+        cache.putIfAbsent(new Element(key, value));
     }
 
     @Override
     public boolean remove(final K key) {
-        return cache.remove(key);
+        checkKey(key);
+        if (isKeyInCache(key)) {
+            cache.remove(key);
+            return true;
+        } else {
+            return false;
+        }
     }
 
     @Override
-    public int size() {
-        return cache.getSize();
+    public void remove(final Function<K, Boolean> keyMatcher) {
+        final Collection<K> toRemove = new HashSet<K>();
+        for (final Object key : getKeys()) {
+            if (keyMatcher.apply((K) key) == Boolean.TRUE) {
+                toRemove.add((K) key);
+            }
+        }
+        cache.removeAll(toRemove);
     }
 
     @Override
@@ -71,16 +113,53 @@ public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> 
     }
 
     @Override
+    public int size() {
+        return cache.getSize();
+    }
+
+    @Override
     public CacheType getCacheType() {
-        return cacheType;
+        return baseCacheLoader.getCacheType();
     }
 
-    private V getWithOrWithoutCacheLoaderArgument(final K key, @Nullable final CacheLoaderArgument cacheLoaderArgument) {
-        final Element element = cacheLoaderArgument != null ? cache.getWithLoader(key, null, cacheLoaderArgument) : cache.get(key);
-        if (element == null || element.getObjectValue() == null || element.getObjectValue().equals(BaseCacheLoader.EMPTY_VALUE_PLACEHOLDER)) {
+    private V computeAndCacheValue(final K key, final CacheLoaderArgument cacheLoaderArgument) {
+        checkKey(key);
+
+        final V value;
+        try {
+            value = baseCacheLoader.compute(key, cacheLoaderArgument);
+        } catch (final Exception e) {
+            logger.warn("Unable to compute cached value for key='{}' and cacheLoaderArgument='{}'", key, cacheLoaderArgument, e);
+            throw new RuntimeException(e);
+        }
+
+        if (value == null) {
             return null;
         }
-        return (V) element.getObjectValue();
+
+        checkValue(value);
+
+        // Race condition, we may compute it for nothing
+        cache.putIfAbsent(new Element(key, value));
+
+        return value;
     }
 
+    private void checkKey(final K keyObject) {
+        if (keyObject == null) {
+            throw new NullPointerException();
+        }
+        if (!getCacheType().getKeyType().isAssignableFrom(keyObject.getClass())) {
+            throw new ClassCastException("Invalid key type, expected : " + getCacheType().getKeyType().getName() + " but was : " + keyObject.getClass().getName());
+        }
+    }
+
+    private void checkValue(final V valueObject) {
+        if (valueObject == null) {
+            throw new NullPointerException();
+        }
+        if (!getCacheType().getValueType().isAssignableFrom(valueObject.getClass())) {
+            throw new ClassCastException("Invalid value type, expected : " + getCacheType().getValueType().getName() + " but was : " + valueObject.getClass().getName());
+        }
+    }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java b/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java
index 305e6f2..9ecb6bb 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2012 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -21,8 +21,7 @@ package org.killbill.billing.util.cache;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.LinkedList;
+import java.util.Set;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
@@ -37,7 +36,6 @@ import com.codahale.metrics.ehcache.InstrumentedEhcache;
 import net.sf.ehcache.CacheException;
 import net.sf.ehcache.CacheManager;
 import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.loader.CacheLoader;
 
 // EhCache specific provider
 public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
@@ -46,43 +44,15 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
 
     private final MetricRegistry metricRegistry;
     private final EhCacheConfig cacheConfig;
-    private final Collection<BaseCacheLoader> cacheLoaders = new LinkedList<BaseCacheLoader>();
+    private final Set<BaseCacheLoader> cacheLoaders;
 
     @Inject
     public EhCacheCacheManagerProvider(final MetricRegistry metricRegistry,
                                        final EhCacheConfig cacheConfig,
-                                       final ImmutableAccountCacheLoader accountCacheLoader,
-                                       final AccountBCDCacheLoader accountBCDCacheLoader,
-                                       final RecordIdCacheLoader recordIdCacheLoader,
-                                       final AccountRecordIdCacheLoader accountRecordIdCacheLoader,
-                                       final TenantRecordIdCacheLoader tenantRecordIdCacheLoader,
-                                       final ObjectIdCacheLoader objectIdCacheLoader,
-                                       final AuditLogCacheLoader auditLogCacheLoader,
-                                       final AuditLogViaHistoryCacheLoader auditLogViaHistoryCacheLoader,
-                                       final TenantCatalogCacheLoader tenantCatalogCacheLoader,
-                                       final TenantConfigCacheLoader tenantConfigCacheLoader,
-                                       final TenantOverdueConfigCacheLoader tenantOverdueConfigCacheLoader,
-                                       final TenantKVCacheLoader tenantKVCacheLoader,
-                                       final TenantCacheLoader tenantCacheLoader,
-                                       final OverriddenPlanCacheLoader overriddenPlanCacheLoader,
-                                       final TenantStateMachineConfigCacheLoader tenantStateMachineConfigCacheLoader) {
+                                       final Set<BaseCacheLoader> cacheLoaders) {
         this.metricRegistry = metricRegistry;
         this.cacheConfig = cacheConfig;
-        cacheLoaders.add(accountCacheLoader);
-        cacheLoaders.add(accountBCDCacheLoader);
-        cacheLoaders.add(recordIdCacheLoader);
-        cacheLoaders.add(accountRecordIdCacheLoader);
-        cacheLoaders.add(tenantRecordIdCacheLoader);
-        cacheLoaders.add(objectIdCacheLoader);
-        cacheLoaders.add(auditLogCacheLoader);
-        cacheLoaders.add(auditLogViaHistoryCacheLoader);
-        cacheLoaders.add(tenantCatalogCacheLoader);
-        cacheLoaders.add(tenantConfigCacheLoader);
-        cacheLoaders.add(tenantOverdueConfigCacheLoader);
-        cacheLoaders.add(tenantKVCacheLoader);
-        cacheLoaders.add(tenantCacheLoader);
-        cacheLoaders.add(overriddenPlanCacheLoader);
-        cacheLoaders.add(tenantStateMachineConfigCacheLoader);
+        this.cacheLoaders = cacheLoaders;
     }
 
     @Override
@@ -98,8 +68,6 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
         }
 
         for (final BaseCacheLoader cacheLoader : cacheLoaders) {
-            cacheLoader.init();
-
             final Ehcache cache = cacheManager.getEhcache(cacheLoader.getCacheType().getCacheName());
 
             if (cache == null) {
@@ -107,12 +75,6 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
                 continue;
             }
 
-            // Make sure we start from a clean state - this is mainly useful for tests
-            for (final CacheLoader existingCacheLoader : cache.getRegisteredCacheLoaders()) {
-                cache.unregisterCacheLoader(existingCacheLoader);
-            }
-            cache.registerCacheLoader(cacheLoader);
-
             // Instrument the cache
             final Ehcache decoratedCache = InstrumentedEhcache.instrument(metricRegistry, cache);
             try {
@@ -121,6 +83,7 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
                 logger.warn("Unable to instrument cache {}: {}", cache.getName(), e.getMessage());
             }
         }
+
         return cacheManager;
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java
index 866c44e..8634a01 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -17,10 +17,11 @@
 
 package org.killbill.billing.util.cache;
 
+import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 
-public class ImmutableAccountCacheLoader extends BaseCacheLoader {
+public class ImmutableAccountCacheLoader extends BaseCacheLoader<Long, ImmutableAccountData> {
 
     @Override
     public CacheType getCacheType() {
@@ -28,30 +29,18 @@ public class ImmutableAccountCacheLoader extends BaseCacheLoader {
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof Long)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
-        }
-
-        final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
-
+    public ImmutableAccountData compute(final Long key, final CacheLoaderArgument cacheLoaderArgument) {
         if (cacheLoaderArgument.getArgs() == null ||
             !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
             throw new IllegalArgumentException("Missing LoaderCallback from the arguments ");
         }
 
         final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
-        return callback.loadAccount((Long) key, cacheLoaderArgument.getInternalTenantContext());
+        return callback.loadAccount(key, cacheLoaderArgument.getInternalTenantContext());
     }
 
     public interface LoaderCallback {
-        Object loadAccount(final Long recordId, final InternalTenantContext context);
+
+        ImmutableAccountData loadAccount(final Long recordId, final InternalTenantContext context);
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/ObjectIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/ObjectIdCacheLoader.java
index fd357ab..cda30b6 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/ObjectIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/ObjectIdCacheLoader.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -17,6 +17,8 @@
 
 package org.killbill.billing.util.cache;
 
+import java.util.UUID;
+
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
@@ -25,10 +27,8 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.skife.jdbi.v2.Handle;
 
-import net.sf.ehcache.loader.CacheLoader;
-
 @Singleton
-public class ObjectIdCacheLoader extends BaseIdCacheLoader implements CacheLoader {
+public class ObjectIdCacheLoader extends BaseIdCacheLoader<UUID> {
 
     private final NonEntityDao nonEntityDao;
 
@@ -44,7 +44,7 @@ public class ObjectIdCacheLoader extends BaseIdCacheLoader implements CacheLoade
     }
 
     @Override
-    protected Object doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
+    protected UUID doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
         final Long recordId = Long.valueOf(rawKey);
         return nonEntityDao.retrieveIdFromObjectInTransaction(recordId, objectType, null, handle);
     }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/OverriddenPlanCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/OverriddenPlanCacheLoader.java
index 52c4a07..79d73b8 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/OverriddenPlanCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/OverriddenPlanCacheLoader.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -22,13 +22,14 @@ import javax.inject.Singleton;
 
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.StaticCatalog;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Singleton
-public class OverriddenPlanCacheLoader extends BaseCacheLoader {
+public class OverriddenPlanCacheLoader extends BaseCacheLoader<String, Plan> {
 
     private final Logger log = LoggerFactory.getLogger(OverriddenPlanCacheLoader.class);
 
@@ -43,18 +44,7 @@ public class OverriddenPlanCacheLoader extends BaseCacheLoader {
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
-        }
-
-
-        final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
+    public Plan compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
         if (cacheLoaderArgument.getArgs() == null || cacheLoaderArgument.getArgs().length != 2) {
             throw new IllegalArgumentException("Invalid arguments for overridden plans");
         }
@@ -66,11 +56,10 @@ public class OverriddenPlanCacheLoader extends BaseCacheLoader {
             throw new IllegalArgumentException("Invalid arguments for overridden plans: missing catalog from argument");
         }
 
-
-        final String planName = (String) key;
+        final String planName = key;
         final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
         final StaticCatalog catalog = (StaticCatalog) cacheLoaderArgument.getArgs()[1];
-        final InternalTenantContext internalTenantContext = ((CacheLoaderArgument) argument).getInternalTenantContext();
+        final InternalTenantContext internalTenantContext = cacheLoaderArgument.getInternalTenantContext();
         try {
             log.info("Loading overridden plan {} for tenant {}", planName, internalTenantContext.getTenantRecordId());
 
@@ -82,6 +71,7 @@ public class OverriddenPlanCacheLoader extends BaseCacheLoader {
     }
 
     public interface LoaderCallback {
-        public Object loadPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException;
+
+        public Plan loadPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException;
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/RecordIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/RecordIdCacheLoader.java
index 08efe2e..a7027a8 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/RecordIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/RecordIdCacheLoader.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2012 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -28,10 +28,8 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.skife.jdbi.v2.Handle;
 
-import net.sf.ehcache.loader.CacheLoader;
-
 @Singleton
-public class RecordIdCacheLoader extends BaseIdCacheLoader implements CacheLoader {
+public class RecordIdCacheLoader extends BaseIdCacheLoader<Long> {
 
     private final NonEntityDao nonEntityDao;
 
@@ -47,7 +45,7 @@ public class RecordIdCacheLoader extends BaseIdCacheLoader implements CacheLoade
     }
 
     @Override
-    protected Object doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
+    protected Long doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
         return nonEntityDao.retrieveRecordIdFromObjectInTransaction(UUID.fromString(rawKey), objectType, null, handle);
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantCacheLoader.java
index bacafa8..1ed03a5 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantCacheLoader.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -26,7 +26,7 @@ import org.killbill.billing.tenant.api.TenantInternalApi;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 
 @Singleton
-public class TenantCacheLoader extends BaseCacheLoader {
+public class TenantCacheLoader extends BaseCacheLoader<String, Tenant> {
 
     private final TenantInternalApi tenantApi;
 
@@ -42,19 +42,10 @@ public class TenantCacheLoader extends BaseCacheLoader {
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
-        }
-
+    public Tenant compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
         try {
-            return tenantApi.getTenantByApiKey((String) key);
-        } catch (TenantApiException e) {
+            return tenantApi.getTenantByApiKey(key);
+        } catch (final TenantApiException e) {
             throw new IllegalStateException("TenantCacheLoader cannot find value for key " + key);
         }
     }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
index b3f9305..03a4ee3 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -23,6 +23,7 @@ import javax.inject.Inject;
 import javax.inject.Singleton;
 
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.tenant.api.TenantInternalApi;
 import org.killbill.billing.util.cache.Cachable.CacheType;
@@ -30,7 +31,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Singleton
-public class TenantCatalogCacheLoader extends BaseCacheLoader {
+public class TenantCatalogCacheLoader extends BaseCacheLoader<Long, Catalog> {
 
     private final Logger log = LoggerFactory.getLogger(TenantCatalogCacheLoader.class);
 
@@ -48,19 +49,9 @@ public class TenantCatalogCacheLoader extends BaseCacheLoader {
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof Long)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
-        }
-
-        final Long tenantRecordId = (Long) key;
+    public Catalog compute(final Long key, final CacheLoaderArgument cacheLoaderArgument) {
+        final Long tenantRecordId = key;
         final InternalTenantContext internalTenantContext = new InternalTenantContext(tenantRecordId);
-        final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
 
         if (cacheLoaderArgument.getArgs() == null || !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
             throw new IllegalArgumentException("Missing LoaderCallback from the arguments ");
@@ -80,6 +71,7 @@ public class TenantCatalogCacheLoader extends BaseCacheLoader {
     }
 
     public interface LoaderCallback {
-        public Object loadCatalog(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException;
+
+        public Catalog loadCatalog(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException;
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantConfigCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantConfigCacheLoader.java
index caced95..d71ad0d 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantConfigCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantConfigCacheLoader.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -25,11 +25,12 @@ import javax.inject.Singleton;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.tenant.api.TenantInternalApi;
 import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.config.tenant.PerTenantConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Singleton
-public class TenantConfigCacheLoader extends BaseCacheLoader {
+public class TenantConfigCacheLoader extends BaseCacheLoader<Long, PerTenantConfig> {
 
     private final Logger log = LoggerFactory.getLogger(TenantConfigCacheLoader.class);
 
@@ -47,19 +48,9 @@ public class TenantConfigCacheLoader extends BaseCacheLoader {
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof Long)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
-        }
-
-        final Long tenantRecordId = (Long) key;
+    public PerTenantConfig compute(final Long key, final CacheLoaderArgument cacheLoaderArgument) {
+        final Long tenantRecordId = key;
         final InternalTenantContext internalTenantContext = new InternalTenantContext(tenantRecordId);
-        final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
 
         if (cacheLoaderArgument.getArgs() == null || !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
             throw new IllegalArgumentException("Missing LoaderCallback from the arguments ");
@@ -77,7 +68,7 @@ public class TenantConfigCacheLoader extends BaseCacheLoader {
     }
 
     public interface LoaderCallback {
-        public Object loadConfig(final String inputJson) throws IOException;
-    }
 
+        public PerTenantConfig loadConfig(final String inputJson) throws IOException;
+    }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantKVCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantKVCacheLoader.java
index a949445..5dcbca5 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantKVCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantKVCacheLoader.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -27,7 +27,7 @@ import org.killbill.billing.tenant.api.TenantInternalApi;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 
 @Singleton
-public class TenantKVCacheLoader extends BaseCacheLoader {
+public class TenantKVCacheLoader extends BaseCacheLoader<String, String> {
 
     private final TenantInternalApi tenantApi;
 
@@ -43,16 +43,8 @@ public class TenantKVCacheLoader extends BaseCacheLoader {
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
-        }
-        final String[] parts = ((String) key).split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
+    public String compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
+        final String[] parts = key.split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
         final String rawKey = parts[0];
         final String tenantRecordId = parts[1];
 
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantOverdueConfigCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantOverdueConfigCacheLoader.java
index 81ebe00..6c95814 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantOverdueConfigCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantOverdueConfigCacheLoader.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -22,13 +22,14 @@ import javax.inject.Singleton;
 
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.overdue.api.OverdueConfig;
 import org.killbill.billing.tenant.api.TenantInternalApi;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Singleton
-public class TenantOverdueConfigCacheLoader extends BaseCacheLoader {
+public class TenantOverdueConfigCacheLoader extends BaseCacheLoader<Long, Object> {
 
     private static final Logger log = LoggerFactory.getLogger(TenantOverdueConfigCacheLoader.class);
 
@@ -46,19 +47,9 @@ public class TenantOverdueConfigCacheLoader extends BaseCacheLoader {
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof Long)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
-        }
-
-        final Long tenantRecordId = (Long) key;
+    public Object compute(final Long key, final CacheLoaderArgument cacheLoaderArgument) {
+        final Long tenantRecordId = key;
         final InternalTenantContext internalTenantContext = new InternalTenantContext(tenantRecordId);
-        final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
 
         if (cacheLoaderArgument.getArgs() == null || !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
             throw new IllegalArgumentException("Missing LoaderCallback from the arguments");
@@ -81,6 +72,6 @@ public class TenantOverdueConfigCacheLoader extends BaseCacheLoader {
 
     public interface LoaderCallback {
 
-        public Object loadOverdueConfig(final String overdueConfigXML) throws OverdueApiException;
+        public OverdueConfig loadOverdueConfig(final String overdueConfigXML) throws OverdueApiException;
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantRecordIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantRecordIdCacheLoader.java
index 17a62af..1144e59 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantRecordIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantRecordIdCacheLoader.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -28,10 +28,8 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.skife.jdbi.v2.Handle;
 
-import net.sf.ehcache.loader.CacheLoader;
-
 @Singleton
-public class TenantRecordIdCacheLoader extends BaseIdCacheLoader implements CacheLoader {
+public class TenantRecordIdCacheLoader extends BaseIdCacheLoader<Long> {
 
     private final NonEntityDao nonEntityDao;
 
@@ -47,7 +45,7 @@ public class TenantRecordIdCacheLoader extends BaseIdCacheLoader implements Cach
     }
 
     @Override
-    protected Object doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
+    protected Long doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
         return nonEntityDao.retrieveTenantRecordIdFromObjectInTransaction(UUID.fromString(rawKey), objectType, null, handle);
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantStateMachineConfigCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantStateMachineConfigCacheLoader.java
index c6d5489..6ef8e00 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantStateMachineConfigCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantStateMachineConfigCacheLoader.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2016-2017 Groupon, Inc
+ * Copyright 2016-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -32,7 +32,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Singleton
-public class TenantStateMachineConfigCacheLoader extends BaseCacheLoader {
+public class TenantStateMachineConfigCacheLoader extends BaseCacheLoader<String, Object> {
 
     private static final Pattern PATTERN = Pattern.compile(TenantKey.PLUGIN_PAYMENT_STATE_MACHINE_.toString() + "(.*)");
     private static final Logger log = LoggerFactory.getLogger(TenantStateMachineConfigCacheLoader.class);
@@ -51,17 +51,8 @@ public class TenantStateMachineConfigCacheLoader extends BaseCacheLoader {
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
-        }
-
-        final String[] parts = ((String) key).split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
+    public Object compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
+        final String[] parts = key.split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
         final String rawKey = parts[0];
         final Matcher matcher = PATTERN.matcher(rawKey);
         if (!matcher.matches()) {
@@ -70,7 +61,6 @@ public class TenantStateMachineConfigCacheLoader extends BaseCacheLoader {
         final String pluginName = matcher.group(1);
         final String tenantRecordId = parts[1];
 
-        final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
         final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
 
         final InternalTenantContext internalTenantContext = new InternalTenantContext(Long.valueOf(tenantRecordId));
diff --git a/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java b/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
index f85796b..5be0ad0 100644
--- a/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
+++ b/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2012 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -32,6 +32,7 @@ import org.killbill.billing.account.api.ImmutableAccountInternalApi;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.clock.Clock;
@@ -50,7 +51,10 @@ public class InternalCallContextFactory {
     private final ImmutableAccountInternalApi accountInternalApi;
     private final Clock clock;
     private final NonEntityDao nonEntityDao;
-    private final CacheControllerDispatcher cacheControllerDispatcher;
+    private final CacheController<String, UUID> objectIdCacheController;
+    private final CacheController<String, Long> recordIdCacheController;
+    private final CacheController<String, Long> accountRecordIdCacheController;
+    private final CacheController<String, Long> tenantRecordIdCacheController;
 
     @Inject
     public InternalCallContextFactory(@Nullable final ImmutableAccountInternalApi accountInternalApi,
@@ -60,7 +64,17 @@ public class InternalCallContextFactory {
         this.accountInternalApi = accountInternalApi;
         this.clock = clock;
         this.nonEntityDao = nonEntityDao;
-        this.cacheControllerDispatcher = cacheControllerDispatcher;
+        if (cacheControllerDispatcher == null) {
+            this.objectIdCacheController = null;
+            this.recordIdCacheController = null;
+            this.accountRecordIdCacheController = null;
+            this.tenantRecordIdCacheController = null;
+        } else {
+            this.objectIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
+            this.recordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
+            this.accountRecordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID);
+            this.tenantRecordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID);
+        }
     }
 
     //
@@ -307,7 +321,7 @@ public class InternalCallContextFactory {
     public UUID getAccountId(final UUID objectId, final ObjectType objectType, final TenantContext context) {
         final Long accountRecordId = getAccountRecordIdSafe(objectId, objectType, context);
         if (accountRecordId != null) {
-            return nonEntityDao.retrieveIdFromObject(accountRecordId, ObjectType.ACCOUNT, cacheControllerDispatcher == null ? null : cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+            return nonEntityDao.retrieveIdFromObject(accountRecordId, ObjectType.ACCOUNT, objectIdCacheController);
         } else {
             return null;
         }
@@ -317,7 +331,7 @@ public class InternalCallContextFactory {
     public Long getRecordIdFromObject(final UUID objectId, final ObjectType objectType, final TenantContext context) {
         try {
             if (objectBelongsToTheRightTenant(objectId, objectType, context)) {
-                return nonEntityDao.retrieveRecordIdFromObject(objectId, objectType, cacheControllerDispatcher == null ? null : cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID));
+                return nonEntityDao.retrieveRecordIdFromObject(objectId, objectType, recordIdCacheController);
             } else {
                 return null;
             }
@@ -358,7 +372,7 @@ public class InternalCallContextFactory {
     }
 
     private UUID getTenantIdSafe(final InternalTenantContext context) {
-        return nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, cacheControllerDispatcher == null ? null : cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+        return nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, objectIdCacheController);
     }
 
     //
@@ -386,11 +400,11 @@ public class InternalCallContextFactory {
     //
 
     private Long getAccountRecordIdUnsafe(final UUID objectId, final ObjectType objectType) {
-        return nonEntityDao.retrieveAccountRecordIdFromObject(objectId, objectType, cacheControllerDispatcher == null ? null : cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID));
+        return nonEntityDao.retrieveAccountRecordIdFromObject(objectId, objectType, accountRecordIdCacheController);
     }
 
     private Long getTenantRecordIdUnsafe(final UUID objectId, final ObjectType objectType) {
-        return nonEntityDao.retrieveTenantRecordIdFromObject(objectId, objectType, cacheControllerDispatcher == null ? null : cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID));
+        return nonEntityDao.retrieveTenantRecordIdFromObject(objectId, objectType, tenantRecordIdCacheController);
     }
 
     public static final class ObjectDoesNotExist extends IllegalStateException {
diff --git a/util/src/main/java/org/killbill/billing/util/config/tenant/CacheConfig.java b/util/src/main/java/org/killbill/billing/util/config/tenant/CacheConfig.java
index 1338b65..789cc0b 100644
--- a/util/src/main/java/org/killbill/billing/util/config/tenant/CacheConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/tenant/CacheConfig.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -34,7 +34,7 @@ import com.google.inject.Inject;
 
 public class CacheConfig {
 
-    private final CacheController cacheController;
+    private final CacheController<Long, PerTenantConfig> cacheController;
     private final CacheLoaderArgument cacheLoaderArgument;
 
     private final ObjectMapper objectMapper;
@@ -48,7 +48,7 @@ public class CacheConfig {
     }
 
     public PerTenantConfig getPerTenantConfig(final InternalTenantContext tenantContext) {
-        final PerTenantConfig perTenantConfig = (PerTenantConfig) cacheController.get(tenantContext.getTenantRecordId(), cacheLoaderArgument);
+        final PerTenantConfig perTenantConfig = cacheController.get(tenantContext.getTenantRecordId(), cacheLoaderArgument);
         return perTenantConfig;
     }
 
@@ -59,7 +59,7 @@ public class CacheConfig {
     private CacheLoaderArgument initializeCacheLoaderArgument() {
         final LoaderCallback loaderCallback = new LoaderCallback() {
             @Override
-            public Object loadConfig(@Nullable final String inputJson) throws IOException {
+            public PerTenantConfig loadConfig(@Nullable final String inputJson) throws IOException {
                 return inputJson != null ? objectMapper.readValue(inputJson, PerTenantConfig.class) : new PerTenantConfig();
             }
         };
@@ -69,5 +69,4 @@ public class CacheConfig {
         final InternalTenantContext notUsed = null;
         return new CacheLoaderArgument(irrelevant, args, notUsed);
     }
-
 }
diff --git a/util/src/main/java/org/killbill/billing/util/dao/DefaultNonEntityDao.java b/util/src/main/java/org/killbill/billing/util/dao/DefaultNonEntityDao.java
index 043805e..728236f 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/DefaultNonEntityDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/DefaultNonEntityDao.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -40,22 +40,22 @@ import com.google.common.base.Preconditions;
 public class DefaultNonEntityDao implements NonEntityDao {
 
     private final NonEntitySqlDao nonEntitySqlDao;
-    private final WithCaching<UUID, Long> withCachingObjectId;
-    private final WithCaching<Long, UUID> withCachingRecordId;
+    private final WithCaching<String, Long> withCachingObjectId;
+    private final WithCaching<String, UUID> withCachingRecordId;
 
     @Inject
     public DefaultNonEntityDao(final IDBI dbi) {
         this.nonEntitySqlDao = dbi.onDemand(NonEntitySqlDao.class);
-        this.withCachingObjectId = new WithCaching<UUID, Long>();
-        this.withCachingRecordId = new WithCaching<Long, UUID>();
+        this.withCachingObjectId = new WithCaching<String, Long>();
+        this.withCachingRecordId = new WithCaching<String, UUID>();
     }
 
     @Override
-    public Long retrieveRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+    public Long retrieveRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache) {
         return retrieveRecordIdFromObjectInTransaction(objectId, objectType, cache, null);
     }
 
-    public Long retrieveRecordIdFromObjectInTransaction(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+    public Long retrieveRecordIdFromObjectInTransaction(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle) {
         if (objectId == null) {
             return null;
         }
@@ -63,28 +63,29 @@ public class DefaultNonEntityDao implements NonEntityDao {
         final TableName tableName = TableName.fromObjectType(objectType);
         Preconditions.checkNotNull(tableName, "%s is not a valid ObjectType", objectType);
 
-        return withCachingObjectId.withCaching(new OperationRetrieval<UUID, Long>() {
+        return withCachingObjectId.withCaching(new OperationRetrieval<Long>() {
             @Override
-            public Long doRetrieve(final UUID objectOrRecordId, final ObjectType objectType) {
+            public Long doRetrieve(final ObjectType objectType) {
                 final NonEntitySqlDao inTransactionNonEntitySqlDao = handle == null ? nonEntitySqlDao : SqlObjectBuilder.attach(handle, NonEntitySqlDao.class);
                 return inTransactionNonEntitySqlDao.getRecordIdFromObject(objectId.toString(), tableName.getTableName());
             }
-        }, objectId, objectType, tableName, cache);
+        }, objectId.toString(), objectType, tableName, cache);
     }
 
     @Override
-    public Long retrieveAccountRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+    public Long retrieveAccountRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache) {
         return retrieveAccountRecordIdFromObjectInTransaction(objectId, objectType, cache, null);
     }
 
     @Override
-    public Long retrieveAccountRecordIdFromObjectInTransaction(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+    public Long retrieveAccountRecordIdFromObjectInTransaction(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle) {
         final TableName tableName = TableName.fromObjectType(objectType);
         Preconditions.checkNotNull(tableName, "%s is not a valid ObjectType", objectType);
 
-        return withCachingObjectId.withCaching(new OperationRetrieval<UUID, Long>() {
+        final String objectIdOrNull = objectId != null ? objectId.toString() : null;
+        return withCachingObjectId.withCaching(new OperationRetrieval<Long>() {
             @Override
-            public Long doRetrieve(final UUID objectId, final ObjectType objectType) {
+            public Long doRetrieve(final ObjectType objectType) {
                 final NonEntitySqlDao inTransactionNonEntitySqlDao = handle == null ? nonEntitySqlDao : SqlObjectBuilder.attach(handle, NonEntitySqlDao.class);
 
                 switch (tableName) {
@@ -94,50 +95,51 @@ public class DefaultNonEntityDao implements NonEntityDao {
                         return null;
 
                     case ACCOUNT:
-                        return inTransactionNonEntitySqlDao.getAccountRecordIdFromAccount(objectId.toString());
+                        return inTransactionNonEntitySqlDao.getAccountRecordIdFromAccount(objectIdOrNull);
 
                     default:
-                        return inTransactionNonEntitySqlDao.getAccountRecordIdFromObjectOtherThanAccount(objectId.toString(), tableName.getTableName());
+                        return inTransactionNonEntitySqlDao.getAccountRecordIdFromObjectOtherThanAccount(objectIdOrNull, tableName.getTableName());
                 }
             }
-        }, objectId, objectType, tableName, cache);
+        }, objectIdOrNull, objectType, tableName, cache);
     }
 
     @Override
-    public Long retrieveTenantRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+    public Long retrieveTenantRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache) {
         return retrieveTenantRecordIdFromObjectInTransaction(objectId, objectType, cache, null);
     }
 
     @Override
-    public Long retrieveTenantRecordIdFromObjectInTransaction(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+    public Long retrieveTenantRecordIdFromObjectInTransaction(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle) {
         final TableName tableName = TableName.fromObjectType(objectType);
         Preconditions.checkNotNull(tableName, "%s is not a valid ObjectType", objectType);
 
-        return withCachingObjectId.withCaching(new OperationRetrieval<UUID, Long>() {
+        final String objectIdOrNull = objectId != null ? objectId.toString() : null;
+        return withCachingObjectId.withCaching(new OperationRetrieval<Long>() {
             @Override
-            public Long doRetrieve(final UUID objectId, final ObjectType objectType) {
+            public Long doRetrieve(final ObjectType objectType) {
                 final NonEntitySqlDao inTransactionNonEntitySqlDao = handle == null ? nonEntitySqlDao : SqlObjectBuilder.attach(handle, NonEntitySqlDao.class);
 
                 switch (tableName) {
                     case TENANT:
                         // Explicit cast to Long to avoid NPE (unboxing to long)
-                        return objectId == null ? (Long) 0L : inTransactionNonEntitySqlDao.getTenantRecordIdFromTenant(objectId.toString());
+                        return objectId == null ? (Long) 0L : inTransactionNonEntitySqlDao.getTenantRecordIdFromTenant(objectIdOrNull);
 
                     default:
-                        return inTransactionNonEntitySqlDao.getTenantRecordIdFromObjectOtherThanTenant(objectId.toString(), tableName.getTableName());
+                        return inTransactionNonEntitySqlDao.getTenantRecordIdFromObjectOtherThanTenant(objectIdOrNull, tableName.getTableName());
                 }
 
             }
-        }, objectId, objectType, tableName, cache);
+        }, objectIdOrNull, objectType, tableName, cache);
     }
 
     @Override
-    public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+    public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<String, UUID> cache) {
         return retrieveIdFromObjectInTransaction(recordId, objectType, cache, null);
     }
 
     @Override
-    public UUID retrieveIdFromObjectInTransaction(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+    public UUID retrieveIdFromObjectInTransaction(final Long recordId, final ObjectType objectType, @Nullable final CacheController<String, UUID> cache, @Nullable final Handle handle) {
         if (objectType == ObjectType.TENANT && recordId == InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID) {
             return null;
         }
@@ -145,13 +147,13 @@ public class DefaultNonEntityDao implements NonEntityDao {
         final TableName tableName = TableName.fromObjectType(objectType);
         Preconditions.checkNotNull(tableName, "%s is not a valid ObjectType", objectType);
 
-        return withCachingRecordId.withCaching(new OperationRetrieval<Long, UUID>() {
+        return withCachingRecordId.withCaching(new OperationRetrieval<UUID>() {
             @Override
-            public UUID doRetrieve(final Long objectOrRecordId, final ObjectType objectType) {
+            public UUID doRetrieve(final ObjectType objectType) {
                 final NonEntitySqlDao inTransactionNonEntitySqlDao = handle == null ? nonEntitySqlDao : SqlObjectBuilder.attach(handle, NonEntitySqlDao.class);
                 return inTransactionNonEntitySqlDao.getIdFromObject(recordId, tableName.getTableName());
             }
-        }, recordId, objectType, tableName, cache);
+        }, String.valueOf(recordId), objectType, tableName, cache);
     }
 
     @Override
@@ -165,31 +167,31 @@ public class DefaultNonEntityDao implements NonEntityDao {
         return nonEntitySqlDao.getHistoryTargetRecordId(recordId, tableName.getTableName());
     }
 
-    private interface OperationRetrieval<TypeIn, TypeOut> {
+    private interface OperationRetrieval<TypeOut> {
 
-        public TypeOut doRetrieve(final TypeIn objectOrRecordId, final ObjectType objectType);
+        public TypeOut doRetrieve(final ObjectType objectType);
     }
 
     // 'cache' will be null for the CacheLoader classes -- or if cache is not configured.
     private class WithCaching<TypeIn, TypeOut> {
 
-        private TypeOut withCaching(final OperationRetrieval<TypeIn, TypeOut> op, @Nullable final TypeIn objectOrRecordId, final ObjectType objectType, final TableName tableName, @Nullable final CacheController<Object, Object> cache) {
+        private TypeOut withCaching(final OperationRetrieval<TypeOut> op, @Nullable final TypeIn objectOrRecordId, final ObjectType objectType, final TableName tableName, @Nullable final CacheController<TypeIn, TypeOut> cache) {
 
             final Profiling<TypeOut, RuntimeException> prof = new Profiling<TypeOut, RuntimeException>();
             if (objectOrRecordId == null) {
                 return null;
             }
             if (cache != null) {
-                final String key = (cache.getCacheType().isKeyPrefixedWithTableName()) ?
-                                   tableName + CacheControllerDispatcher.CACHE_KEY_SEPARATOR + objectOrRecordId.toString() :
-                                   objectOrRecordId.toString();
-                return (TypeOut) cache.get(key, new CacheLoaderArgument(objectType));
+                final TypeIn key = (cache.getCacheType().isKeyPrefixedWithTableName()) ?
+                                   (TypeIn) (tableName + CacheControllerDispatcher.CACHE_KEY_SEPARATOR + objectOrRecordId.toString()) :
+                                   objectOrRecordId;
+                return cache.get(key, new CacheLoaderArgument(objectType));
             }
             final TypeOut result;
             result = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, "NonEntityDao (type = " + objectType + ") cache miss", new WithProfilingCallback<TypeOut, RuntimeException>() {
                 @Override
                 public TypeOut execute() throws RuntimeException {
-                    return op.doRetrieve(objectOrRecordId, objectType);
+                    return op.doRetrieve(objectType);
                 }
             });
             return result;
diff --git a/util/src/main/java/org/killbill/billing/util/dao/NonEntityDao.java b/util/src/main/java/org/killbill/billing/util/dao/NonEntityDao.java
index 72c9cf2..0261192 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/NonEntityDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/NonEntityDao.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -29,21 +29,21 @@ import org.skife.jdbi.v2.Handle;
 // This should only be used for internal operations (trusted code, not API), because the context will not be validated!
 public interface NonEntityDao {
 
-    public Long retrieveRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache);
+    public Long retrieveRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache);
 
-    public Long retrieveRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle);
+    public Long retrieveRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle);
 
-    public Long retrieveAccountRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache);
+    public Long retrieveAccountRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache);
 
-    public Long retrieveAccountRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle);
+    public Long retrieveAccountRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle);
 
-    public Long retrieveTenantRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache);
+    public Long retrieveTenantRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache);
 
-    public Long retrieveTenantRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle);
+    public Long retrieveTenantRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle);
 
-    public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache);
+    public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<String, UUID> cache);
 
-    public UUID retrieveIdFromObjectInTransaction(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle);
+    public UUID retrieveIdFromObjectInTransaction(final Long recordId, final ObjectType objectType, @Nullable final CacheController<String, UUID> cache, @Nullable final Handle handle);
 
     // This retrieves from the history table the latest record for which targetId matches the one we are passing
     public Long retrieveLastHistoryRecordIdFromTransaction(final Long targetRecordId, final TableName tableName, final NonEntitySqlDao transactional);
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
index 872ba10..280d8ac 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2012 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -30,6 +30,7 @@ import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 
 import javax.annotation.Nullable;
 
@@ -344,20 +345,20 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
 
     private void populateCacheOnGetByIdInvocation(M model) {
 
-        final CacheController<Object, Object> cacheRecordId = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
-        cacheRecordId.add(getKey(model.getId().toString(), CacheType.RECORD_ID, model.getTableName()), model.getRecordId());
+        final CacheController<String, Long> cacheRecordId = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
+        cacheRecordId.putIfAbsent(getKey(model.getId().toString(), CacheType.RECORD_ID, model.getTableName()), model.getRecordId());
 
-        final CacheController<Object, Object> cacheObjectId = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
-        cacheObjectId.add(getKey(model.getRecordId().toString(), CacheType.OBJECT_ID, model.getTableName()), model.getId());
+        final CacheController<String, UUID> cacheObjectId = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
+        cacheObjectId.putIfAbsent(getKey(model.getRecordId().toString(), CacheType.OBJECT_ID, model.getTableName()), model.getId());
 
         if (model.getTenantRecordId() != null) {
-            final CacheController<Object, Object> cacheTenantRecordId = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID);
-            cacheTenantRecordId.add(getKey(model.getId().toString(), CacheType.TENANT_RECORD_ID, model.getTableName()), model.getTenantRecordId());
+            final CacheController<String, Long> cacheTenantRecordId = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID);
+            cacheTenantRecordId.putIfAbsent(getKey(model.getId().toString(), CacheType.TENANT_RECORD_ID, model.getTableName()), model.getTenantRecordId());
         }
 
         if (model.getAccountRecordId() != null) {
-            final CacheController<Object, Object> cacheAccountRecordId = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID);
-            cacheAccountRecordId.add(getKey(model.getId().toString(), CacheType.ACCOUNT_RECORD_ID, model.getTableName()), model.getAccountRecordId());
+            final CacheController<String, Long> cacheAccountRecordId = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID);
+            cacheAccountRecordId.putIfAbsent(getKey(model.getId().toString(), CacheType.ACCOUNT_RECORD_ID, model.getTableName()), model.getAccountRecordId());
         }
     }
 
@@ -478,13 +479,13 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
         // We need to invalidate the caches. There is a small window of doom here where caches will be stale.
         // TODO Knowledge on how the key is constructed is also in AuditSqlDao
         if (tableName.getHistoryTableName() != null) {
-            final CacheController<Object, Object> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG_VIA_HISTORY);
+            final CacheController<String, List> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG_VIA_HISTORY);
             if (cacheController != null) {
                 final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName.getHistoryTableName(), 1, tableName.getHistoryTableName(), 2, entityRecordId));
                 cacheController.remove(key);
             }
         } else {
-            final CacheController<Object, Object> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG);
+            final CacheController<String, List> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG);
             if (cacheController != null) {
                 final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName, 1, entityRecordId));
                 cacheController.remove(key);
diff --git a/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java b/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
index 5a3b660..4ea728c 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
@@ -19,12 +19,29 @@
 package org.killbill.billing.util.glue;
 
 import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.cache.AccountBCDCacheLoader;
+import org.killbill.billing.util.cache.AccountRecordIdCacheLoader;
+import org.killbill.billing.util.cache.AuditLogCacheLoader;
+import org.killbill.billing.util.cache.AuditLogViaHistoryCacheLoader;
+import org.killbill.billing.util.cache.BaseCacheLoader;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.cache.CacheControllerDispatcherProvider;
 import org.killbill.billing.util.cache.EhCacheCacheManagerProvider;
+import org.killbill.billing.util.cache.ImmutableAccountCacheLoader;
+import org.killbill.billing.util.cache.ObjectIdCacheLoader;
+import org.killbill.billing.util.cache.OverriddenPlanCacheLoader;
+import org.killbill.billing.util.cache.RecordIdCacheLoader;
+import org.killbill.billing.util.cache.TenantCacheLoader;
+import org.killbill.billing.util.cache.TenantCatalogCacheLoader;
+import org.killbill.billing.util.cache.TenantConfigCacheLoader;
+import org.killbill.billing.util.cache.TenantKVCacheLoader;
+import org.killbill.billing.util.cache.TenantOverdueConfigCacheLoader;
+import org.killbill.billing.util.cache.TenantRecordIdCacheLoader;
+import org.killbill.billing.util.cache.TenantStateMachineConfigCacheLoader;
 import org.killbill.billing.util.config.definition.EhCacheConfig;
 import org.skife.config.ConfigurationObjectFactory;
 
+import com.google.inject.multibindings.Multibinder;
 import net.sf.ehcache.CacheManager;
 
 public class CacheModule extends KillBillModule {
@@ -43,5 +60,22 @@ public class CacheModule extends KillBillModule {
 
         // Kill Bill generic cache dispatcher
         bind(CacheControllerDispatcher.class).toProvider(CacheControllerDispatcherProvider.class).asEagerSingleton();
+
+        final Multibinder<BaseCacheLoader> resultSetMapperSetBinder = Multibinder.newSetBinder(binder(), BaseCacheLoader.class);
+        resultSetMapperSetBinder.addBinding().to(ImmutableAccountCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(AccountBCDCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(RecordIdCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(AccountRecordIdCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(TenantRecordIdCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(ObjectIdCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(AuditLogCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(AuditLogViaHistoryCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(TenantCatalogCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(TenantConfigCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(TenantOverdueConfigCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(TenantKVCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(TenantCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(OverriddenPlanCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(TenantStateMachineConfigCacheLoader.class).asEagerSingleton();
     }
 }
diff --git a/util/src/test/java/org/killbill/billing/dao/MockNonEntityDao.java b/util/src/test/java/org/killbill/billing/dao/MockNonEntityDao.java
index 5893704..0a1812e 100644
--- a/util/src/test/java/org/killbill/billing/dao/MockNonEntityDao.java
+++ b/util/src/test/java/org/killbill/billing/dao/MockNonEntityDao.java
@@ -46,42 +46,42 @@ public class MockNonEntityDao implements NonEntityDao {
     }
 
     @Override
-    public Long retrieveRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+    public Long retrieveRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache) {
         return null;
     }
 
     @Override
-    public Long retrieveRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+    public Long retrieveRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle) {
         return null;
     }
 
     @Override
-    public Long retrieveAccountRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+    public Long retrieveAccountRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache) {
         return accountRecordIdMappings.get(objectId);
     }
 
     @Override
-    public Long retrieveAccountRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+    public Long retrieveAccountRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle) {
         return null;
     }
 
     @Override
-    public Long retrieveTenantRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+    public Long retrieveTenantRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache) {
         return tenantRecordIdMappings.get(objectId);
     }
 
     @Override
-    public Long retrieveTenantRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+    public Long retrieveTenantRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle) {
         return null;
     }
 
     @Override
-    public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+    public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<String, UUID> cache) {
         return null;
     }
 
     @Override
-    public UUID retrieveIdFromObjectInTransaction(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+    public UUID retrieveIdFromObjectInTransaction(final Long recordId, final ObjectType objectType, @Nullable final CacheController<String, UUID> cache, @Nullable final Handle handle) {
         return null;
     }