killbill-uncached

Merge pull request #1052 from killbill/caching-improvements cache:

10/19/2018 11:54:02 AM

Changes

.idea/compiler.xml 38(+19 -19)

payment/src/main/java/org/killbill/billing/payment/caching/SerializableStateMachineConfig.java 80(+0 -80)

pom.xml 2(+1 -1)

util/src/main/java/org/killbill/billing/util/cache/ExternalizableInput.java 67(+0 -67)

util/src/main/java/org/killbill/billing/util/cache/ExternalizableOutput.java 57(+0 -57)

util/src/main/java/org/killbill/billing/util/cache/MapperHolder.java 44(+0 -44)

Details

diff --git a/.circleci/config.yml b/.circleci/config.yml
index fa5db56..df90966 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -43,7 +43,7 @@ jobs:
       - checkout
       - restore_cache:
           key: v1-dependencies-{{ .Branch }}-{{ checksum "pom.xml" }}
-      - run: mvn clean install -Ptravis
+      - run: mvn -Djava.security.egd=file:/dev/./urandom clean install -Ptravis
       - run:
           name: Save test results
           command: |
@@ -82,7 +82,7 @@ jobs:
 
             set -e
             ./bin/db-helper -a create --driver mysql -u root -p root -t yes -h 127.0.0.1
-      - run: mvn clean install -Plocaltest-mysql
+      - run: mvn -Djava.security.egd=file:/dev/./urandom clean install -Plocaltest-mysql
       - run:
           name: Save test results
           command: |
@@ -107,7 +107,7 @@ jobs:
       - run:
           name: Setup latest DDL
           command: ./bin/db-helper -a create --driver postgres -u postgres -p postgres -t yes
-      - run: mvn clean install -Plocaltest-postgresql
+      - run: mvn -Djava.security.egd=file:/dev/./urandom clean install -Plocaltest-postgresql
       - run:
           name: Save test results
           command: |

.idea/compiler.xml 38(+19 -19)

diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index cb68ce5..721e7df 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -36,25 +36,25 @@
       </profile>
     </annotationProcessing>
     <bytecodeTargetLevel>
-      <module name="killbill" target="1.6" />
-      <module name="killbill-account" target="1.6" />
-      <module name="killbill-beatrix" target="1.6" />
-      <module name="killbill-catalog" target="1.6" />
-      <module name="killbill-currency" target="1.6" />
-      <module name="killbill-entitlement" target="1.6" />
-      <module name="killbill-internal-api" target="1.6" />
-      <module name="killbill-invoice" target="1.6" />
-      <module name="killbill-jaxrs" target="1.6" />
-      <module name="killbill-junction" target="1.6" />
-      <module name="killbill-overdue" target="1.6" />
-      <module name="killbill-payment" target="1.6" />
-      <module name="killbill-profiles" target="1.6" />
-      <module name="killbill-profiles-killbill" target="1.6" />
-      <module name="killbill-profiles-killpay" target="1.6" />
-      <module name="killbill-subscription" target="1.6" />
-      <module name="killbill-tenant" target="1.6" />
-      <module name="killbill-usage" target="1.6" />
-      <module name="killbill-util" target="1.6" />
+      <module name="killbill" target="1.8" />
+      <module name="killbill-account" target="1.8" />
+      <module name="killbill-beatrix" target="1.8" />
+      <module name="killbill-catalog" target="1.8" />
+      <module name="killbill-currency" target="1.8" />
+      <module name="killbill-entitlement" target="1.8" />
+      <module name="killbill-internal-api" target="1.8" />
+      <module name="killbill-invoice" target="1.8" />
+      <module name="killbill-jaxrs" target="1.8" />
+      <module name="killbill-junction" target="1.8" />
+      <module name="killbill-overdue" target="1.8" />
+      <module name="killbill-payment" target="1.8" />
+      <module name="killbill-profiles" target="1.8" />
+      <module name="killbill-profiles-killbill" target="1.8" />
+      <module name="killbill-profiles-killpay" target="1.8" />
+      <module name="killbill-subscription" target="1.8" />
+      <module name="killbill-tenant" target="1.8" />
+      <module name="killbill-usage" target="1.8" />
+      <module name="killbill-util" target="1.8" />
     </bytecodeTargetLevel>
   </component>
   <component name="JavacSettings">
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index a106408..3e1dd16 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -1,10 +1,11 @@
 <component name="InspectionProjectProfileManager">
   <profile version="1.0" is_locked="false">
     <option name="myName" value="Project Default" />
-    <option name="myLocal" value="false" />
+    <inspection_tool class="Anonymous2MethodRef" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="CheckTagEmptyBody" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ConfusingOctalEscape" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="Convert2Lambda" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="CyclicClassDependency" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="FieldMayBeFinal" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="HardcodedLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
@@ -24,11 +25,13 @@
     </inspection_tool>
     <inspection_tool class="RedundantTypeArguments" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="TryWithIdenticalCatches" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="TypeMayBeWeakened" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="useRighthandTypeAsWeakestTypeInAssignments" value="true" />
       <option name="useParameterizedTypeForCollectionMethods" value="true" />
       <option name="doNotWeakenToJavaLangObject" value="true" />
       <option name="onlyWeakentoInterface" value="true" />
+      <stopClasses>org.killbill.billing.catalog.api.Plan</stopClasses>
     </inspection_tool>
     <inspection_tool class="UnnecessaryConstantArrayCreationExpression" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="UseOfJDBCDriverClass" enabled="true" level="WARNING" enabled_by_default="true" />
diff --git a/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java b/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java
index fdce2f1..0c58a3a 100644
--- a/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java
+++ b/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java
@@ -28,9 +28,6 @@ import org.joda.time.DateTimeZone;
 import org.killbill.billing.account.dao.AccountModelDao;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.util.account.AccountDateTimeUtils;
-import org.killbill.billing.util.cache.ExternalizableInput;
-import org.killbill.billing.util.cache.ExternalizableOutput;
-import org.killbill.billing.util.cache.MapperHolder;
 
 public class DefaultImmutableAccountData implements ImmutableAccountData, Externalizable {
 
@@ -157,11 +154,25 @@ public class DefaultImmutableAccountData implements ImmutableAccountData, Extern
 
     @Override
     public void readExternal(final ObjectInput in) throws IOException {
-        MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+        this.id = new UUID(in.readLong(), in.readLong());
+        this.externalKey = in.readUTF();
+        this.currency = in.readBoolean() ? Currency.valueOf(in.readUTF()) : null;
+        this.timeZone = DateTimeZone.forID(in.readUTF());
+        this.fixedOffsetTimeZone = DateTimeZone.forID(in.readUTF());
+        this.referenceTime = new DateTime(in.readUTF());
     }
 
     @Override
     public void writeExternal(final ObjectOutput oo) throws IOException {
-        MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+        oo.writeLong(id.getMostSignificantBits());
+        oo.writeLong(id.getLeastSignificantBits());
+        oo.writeUTF(externalKey);
+        oo.writeBoolean(currency != null);
+        if (currency != null) {
+            oo.writeUTF(currency.name());
+        }
+        oo.writeUTF(timeZone.getID());
+        oo.writeUTF(fixedOffsetTimeZone.getID());
+        oo.writeUTF(referenceTime.toString());
     }
 }
diff --git a/api/src/main/java/org/killbill/billing/callcontext/CallContextBase.java b/api/src/main/java/org/killbill/billing/callcontext/CallContextBase.java
index 2f5b334..0fe2ce0 100644
--- a/api/src/main/java/org/killbill/billing/callcontext/CallContextBase.java
+++ b/api/src/main/java/org/killbill/billing/callcontext/CallContextBase.java
@@ -16,6 +16,10 @@
 
 package org.killbill.billing.callcontext;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -24,19 +28,19 @@ import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.CallOrigin;
 import org.killbill.billing.util.callcontext.UserType;
 
-public abstract class CallContextBase implements CallContext {
+public abstract class CallContextBase implements CallContext, Externalizable {
 
-    protected final UUID accountId;
-    protected final UUID tenantId;
-    protected final UUID userToken;
-    protected final String userName;
-    protected final CallOrigin callOrigin;
-    protected final UserType userType;
-    protected final String reasonCode;
-    protected final String comments;
+    protected UUID accountId;
+    protected UUID tenantId;
+    protected UUID userToken;
+    protected String userName;
+    protected CallOrigin callOrigin;
+    protected UserType userType;
+    protected String reasonCode;
+    protected String comments;
 
-    public CallContextBase(@Nullable final UUID accountId, @Nullable final UUID tenantId, final String userName, final CallOrigin callOrigin, final UserType userType) {
-        this(accountId, tenantId, userName, callOrigin, userType, null);
+    // For deserialization
+    public CallContextBase() {
     }
 
     public CallContextBase(@Nullable final UUID accountId, @Nullable final UUID tenantId, final String userName, final CallOrigin callOrigin, final UserType userType, final UUID userToken) {
@@ -94,4 +98,37 @@ public abstract class CallContextBase implements CallContext {
     public UUID getUserToken() {
         return userToken;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeLong(accountId == null ? 0 : accountId.getMostSignificantBits());
+        out.writeLong(accountId == null ? 0 : accountId.getLeastSignificantBits());
+        out.writeLong(tenantId == null ? 0 : tenantId.getMostSignificantBits());
+        out.writeLong(tenantId == null ? 0 : tenantId.getLeastSignificantBits());
+        out.writeLong(userToken.getMostSignificantBits());
+        out.writeLong(userToken.getLeastSignificantBits());
+        out.writeUTF(userName);
+        out.writeUTF(callOrigin.name());
+        out.writeUTF(userType.name());
+        out.writeUTF(reasonCode);
+        out.writeUTF(comments);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.accountId = new UUID(in.readLong(), in.readLong());
+        if (this.accountId.getMostSignificantBits() == 0) {
+            this.accountId = null;
+        }
+        this.tenantId = new UUID(in.readLong(), in.readLong());
+        if (this.tenantId.getMostSignificantBits() == 0) {
+            this.tenantId = null;
+        }
+        this.userToken = new UUID(in.readLong(), in.readLong());
+        this.userName = in.readUTF();
+        this.callOrigin = CallOrigin.valueOf(in.readUTF());
+        this.userType = UserType.valueOf(in.readUTF());
+        this.reasonCode = in.readUTF();
+        this.comments = in.readUTF();
+    }
 }
diff --git a/api/src/main/java/org/killbill/billing/callcontext/DefaultCallContext.java b/api/src/main/java/org/killbill/billing/callcontext/DefaultCallContext.java
index c12b759..13fb6e7 100644
--- a/api/src/main/java/org/killbill/billing/callcontext/DefaultCallContext.java
+++ b/api/src/main/java/org/killbill/billing/callcontext/DefaultCallContext.java
@@ -16,19 +16,26 @@
 
 package org.killbill.billing.callcontext;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.CallOrigin;
 import org.killbill.billing.util.callcontext.UserType;
 import org.killbill.clock.Clock;
 
-public class DefaultCallContext extends CallContextBase {
+public class DefaultCallContext extends CallContextBase implements Externalizable {
+
+    private DateTime createdDate;
+    private DateTime updateDate;
 
-    private final DateTime createdDate;
-    private final DateTime updateDate;
+    // For deserialization
+    public DefaultCallContext() {
+    }
 
     public DefaultCallContext(final UUID accountId, final UUID tenantId, final String userName, final CallOrigin callOrigin, final UserType userType,
                               final UUID userToken, final Clock clock) {
@@ -148,4 +155,18 @@ public class DefaultCallContext extends CallContextBase {
         result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        super.writeExternal(out);
+        out.writeUTF(createdDate.toString());
+        out.writeUTF(updateDate.toString());
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        super.readExternal(in);
+        this.createdDate = new DateTime(in.readUTF());
+        this.updateDate = new DateTime(in.readUTF());
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/api/user/DefaultCatalogUserApi.java b/catalog/src/main/java/org/killbill/billing/catalog/api/user/DefaultCatalogUserApi.java
index a058104..d883ea4 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/api/user/DefaultCatalogUserApi.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/api/user/DefaultCatalogUserApi.java
@@ -19,8 +19,6 @@ package org.killbill.billing.catalog.api.user;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
 
 import javax.annotation.Nullable;
 import javax.inject.Inject;
@@ -65,7 +63,6 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
     private final CatalogCache catalogCache;
     private final Clock clock;
 
-
     @Inject
     public DefaultCatalogUserApi(final CatalogService catalogService,
                                  final TenantUserApi tenantApi,
@@ -116,7 +113,6 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
     @Override
     public void uploadCatalog(final String catalogXML, final CallContext callContext) throws CatalogApiException {
 
-
         final InternalTenantContext internalTenantContext = createInternalTenantContext(callContext);
         try {
 
@@ -124,7 +120,7 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
 
             // Validation purpose:  Will throw if bad XML or catalog validation fails
             final InputStream stream = new ByteArrayInputStream(catalogXML.getBytes());
-            final StaticCatalog newCatalogVersion = XMLLoader.getObjectFromStream(new URI("dummy"), stream, StandaloneCatalog.class);
+            final StaticCatalog newCatalogVersion = XMLLoader.getObjectFromStream(stream, StandaloneCatalog.class);
 
             if (versionedCatalog != null) {
 
@@ -132,7 +128,7 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
                 if (versionedCatalog.getCatalogName() != null && !newCatalogVersion.getCatalogName().equals(versionedCatalog.getCatalogName())) {
                     final ValidationErrors errors = new ValidationErrors();
                     errors.add(String.format("Catalog name '%s' should match previous catalog name '%s'", newCatalogVersion.getCatalogName(), versionedCatalog.getCatalogName()),
-                            new URI("dummy"), StandaloneCatalog.class, "");
+                               StandaloneCatalog.class, "");
                     // Bummer ValidationException CTOR is private to package...
                     //final ValidationException validationException = new ValidationException(errors);
                     //throw new CatalogApiException(errors, ErrorCode.CAT_INVALID_FOR_TENANT, internalTenantContext.getTenantRecordId());
@@ -144,7 +140,7 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
                     if (c.getEffectiveDate().compareTo(newCatalogVersion.getEffectiveDate()) == 0) {
                         final ValidationErrors errors = new ValidationErrors();
                         errors.add(String.format("Catalog version for effectiveDate '%s' already exists", newCatalogVersion.getEffectiveDate()),
-                                new URI("dummy"), StandaloneCatalog.class, "");
+                                   StandaloneCatalog.class, "");
                         // Bummer ValidationException CTOR is private to package...
                         //final ValidationException validationException = new ValidationException(errors);
                         //throw new CatalogApiException(errors, ErrorCode.CAT_INVALID_FOR_TENANT, internalTenantContext.getTenantRecordId());
@@ -166,17 +162,13 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
             throw new IllegalStateException(e);
         } catch (final TransformerException e) {
             throw new IllegalStateException(e);
-        } catch (final URISyntaxException e) {
-            throw new IllegalStateException(e);
         } catch (final SAXException e) {
             throw new IllegalStateException(e);
         } catch (final InvalidConfigException e) {
             throw new IllegalStateException(e);
         }
-
     }
 
-
     @Override
     public void createDefaultEmptyCatalog(@Nullable final DateTime effectiveDate, final CallContext callContext) throws CatalogApiException {
 
@@ -224,6 +216,7 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
             throw new CatalogApiException(e);
         }
     }
+
     private DateTime getSafeFirstCatalogEffectiveDate(@Nullable final DateTime input, final CallContext callContext) {
         // The effectiveDate for the initial version does not matter too much
         // Because of #760, we want to make that client passing a approximate date (e.g today.toDateTimeAtStartOfDay()) will find the version
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/caching/OverriddenPlanCache.java b/catalog/src/main/java/org/killbill/billing/catalog/caching/OverriddenPlanCache.java
index f888dc8..21d4e74 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/caching/OverriddenPlanCache.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/caching/OverriddenPlanCache.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -19,13 +19,13 @@ package org.killbill.billing.catalog.caching;
 
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.DefaultPlan;
+import org.killbill.billing.catalog.StandaloneCatalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Plan;
-import org.killbill.billing.catalog.api.StaticCatalog;
 
 public interface OverriddenPlanCache {
 
-    DefaultPlan getOverriddenPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException;
+    DefaultPlan getOverriddenPlan(final String planName, final StandaloneCatalog catalog, final InternalTenantContext context) throws CatalogApiException;
 
     void addDryRunPlan(final String planName, final Plan plan);
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/CatalogEntityCollection.java b/catalog/src/main/java/org/killbill/billing/catalog/CatalogEntityCollection.java
index d2d2cbd..e797ce4 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/CatalogEntityCollection.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/CatalogEntityCollection.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,10 @@
 
 package org.killbill.billing.catalog;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.Map;
@@ -26,7 +30,7 @@ import org.killbill.billing.catalog.api.CatalogEntity;
 
 import com.google.common.collect.Ordering;
 
-public class CatalogEntityCollection<T extends CatalogEntity> implements Collection<T> {
+public class CatalogEntityCollection<T extends CatalogEntity> implements Collection<T>, Externalizable {
 
     private final Map<String, T> data;
 
@@ -41,7 +45,6 @@ public class CatalogEntityCollection<T extends CatalogEntity> implements Collect
         }
     }
 
-
     public CatalogEntityCollection(final Iterable<T> entities) {
         this.data = new TreeMap<String, T>(Ordering.<String>natural());
         for (final T cur : entities) {
@@ -82,15 +85,18 @@ public class CatalogEntityCollection<T extends CatalogEntity> implements Collect
         final Iterator<String> keyIterator = data.keySet().iterator();
         final Iterator it = new Iterator() {
             private String prevKey = null;
+
             @Override
             public boolean hasNext() {
                 return keyIterator.hasNext();
             }
+
             @Override
             public Object next() {
                 prevKey = keyIterator.next();
                 return data.get(prevKey);
             }
+
             @Override
             public void remove() {
                 if (prevKey != null) {
@@ -191,4 +197,13 @@ public class CatalogEntityCollection<T extends CatalogEntity> implements Collect
         return data.remove(entry.getName()) != null;
     }
 
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        data.putAll((Map<? extends String, ? extends T>) in.readObject());
+    }
+
+    @Override
+    public void writeExternal(final ObjectOutput oo) throws IOException {
+        oo.writeObject(data);
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/CatalogUpdater.java b/catalog/src/main/java/org/killbill/billing/catalog/CatalogUpdater.java
index f580cd3..a301784 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/CatalogUpdater.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/CatalogUpdater.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,8 +18,6 @@
 package org.killbill.billing.catalog;
 
 import java.math.BigDecimal;
-import java.net.URI;
-import java.net.URISyntaxException;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.ErrorCode;
@@ -53,17 +51,6 @@ public class CatalogUpdater {
 
     public static String DEFAULT_CATALOG_NAME = "DEFAULT";
 
-    private static URI DEFAULT_URI;
-
-    static {
-        try {
-            DEFAULT_URI = new URI(DEFAULT_CATALOG_NAME);
-        } catch (URISyntaxException e) {
-        }
-    }
-
-    ;
-
     private final DefaultMutableStaticCatalog catalog;
 
     public CatalogUpdater(final StandaloneCatalog standaloneCatalog) {
@@ -88,7 +75,7 @@ public class CatalogUpdater {
         } else {
             tmp.setSupportedCurrencies(new Currency[0]);
         }
-        tmp.initialize(tmp, DEFAULT_URI);
+        tmp.initialize(tmp);
 
         this.catalog = new DefaultMutableStaticCatalog(tmp);
     }
@@ -119,12 +106,12 @@ public class CatalogUpdater {
 
         validateNewPlanDescriptor(desc);
 
-        DefaultProduct product = plan != null ? (DefaultProduct) plan.getProduct() : (DefaultProduct)  getExistingProduct(desc.getProductName());
+        DefaultProduct product = plan != null ? (DefaultProduct) plan.getProduct() : (DefaultProduct) getExistingProduct(desc.getProductName());
         if (product == null) {
             product = new DefaultProduct();
             product.setName(desc.getProductName());
             product.setCatagory(desc.getProductCategory());
-            product.initialize(catalog, DEFAULT_URI);
+            product.initialize(catalog);
             catalog.addProduct(product);
         }
 
@@ -197,7 +184,7 @@ public class CatalogUpdater {
         }
 
         // Reinit catalog
-        catalog.initialize(catalog, DEFAULT_URI);
+        catalog.initialize(catalog);
     }
 
     private boolean isPriceForCurrencyExists(final InternationalPrice price, final Currency currency) {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
index 8e62c6c..ff42b1f 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
@@ -17,8 +17,11 @@
 
 package org.killbill.billing.catalog;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.math.BigDecimal;
-import java.net.URI;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -39,7 +42,7 @@ import org.killbill.xmlloader.ValidationError;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultBlock extends ValidatingConfig<StandaloneCatalog> implements Block {
+public class DefaultBlock extends ValidatingConfig<StandaloneCatalog> implements Block, Externalizable {
 
     @XmlAttribute(required = false)
     private BlockType type = BlockType.VANILLA;
@@ -97,7 +100,7 @@ public class DefaultBlock extends ValidatingConfig<StandaloneCatalog> implements
 
         if (type == BlockType.TOP_UP && CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE.equals(minTopUpCredit)) {
             errors.add(new ValidationError(String.format("TOP_UP block needs to define minTopUpCredit for phase %s",
-                                                         phase.getName()), catalog.getCatalogURI(), DefaultUsage.class, ""));
+                                                         phase.getName()), DefaultUsage.class, ""));
         }
         return errors;
     }
@@ -112,8 +115,8 @@ public class DefaultBlock extends ValidatingConfig<StandaloneCatalog> implements
     }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
     }
 
@@ -189,4 +192,31 @@ public class DefaultBlock extends ValidatingConfig<StandaloneCatalog> implements
         result = 31 * result + (minTopUpCredit != null ? minTopUpCredit.hashCode() : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeBoolean(type != null);
+        if (type != null) {
+            out.writeUTF(type.name());
+        }
+        out.writeObject(unit);
+        out.writeBoolean(size != null);
+        if (size != null) {
+            out.writeDouble(size);
+        }
+        out.writeObject(prices);
+        out.writeBoolean(minTopUpCredit != null);
+        if (minTopUpCredit != null) {
+            out.writeDouble(minTopUpCredit);
+        }
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.type = in.readBoolean() ? BlockType.valueOf(in.readUTF()) : null;
+        this.unit = (DefaultUnit) in.readObject();
+        this.size = in.readBoolean() ? in.readDouble() : null;
+        this.prices = (DefaultInternationalPrice) in.readObject();
+        this.minTopUpCredit = in.readBoolean() ? in.readDouble() : null;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
index 7af3daa..f53ebee 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,7 +18,10 @@
 
 package org.killbill.billing.catalog;
 
-import java.net.URI;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -25,7 +30,6 @@ import javax.xml.bind.annotation.XmlElement;
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.joda.time.Period;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Duration;
@@ -35,7 +39,7 @@ import org.killbill.xmlloader.ValidationError;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> implements Duration {
+public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> implements Duration, Externalizable {
 
     @XmlElement(required = true)
     private TimeUnit unit;
@@ -43,22 +47,17 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
     @XmlElement(required = false)
     private Integer number;
 
-    /* (non-Javadoc)
-      * @see org.killbill.billing.catalog.IDuration#getUnit()
-      */
     @Override
     public TimeUnit getUnit() {
         return unit;
     }
 
-    /* (non-Javadoc)
-	 * @see org.killbill.billing.catalog.IDuration#getLength()
-	 */
     @Override
     public int getNumber() {
         return number;
     }
 
+    // Required for deserialization
     public DefaultDuration() {
     }
 
@@ -115,21 +114,20 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
         //Validation: TimeUnit UNLIMITED if number == -1
         if ((unit == TimeUnit.UNLIMITED && !CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE.equals(number))) {
             errors.add(new ValidationError("Duration can only have 'UNLIMITED' unit if the number is omitted",
-                                           catalog.getCatalogURI(), DefaultDuration.class, ""));
+                                           DefaultDuration.class, ""));
         } else if ((unit != TimeUnit.UNLIMITED) && CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE.equals(number)) {
             errors.add(new ValidationError("Finite Duration must have a well defined length",
-                                           catalog.getCatalogURI(), DefaultDuration.class, ""));
+                                           DefaultDuration.class, ""));
         }
         return errors;
     }
 
     @Override
-    public void initialize(final StandaloneCatalog root, final URI uri) {
-        super.initialize(root, uri);
+    public void initialize(final StandaloneCatalog root) {
+        super.initialize(root);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
     }
 
-
     public DefaultDuration setUnit(final TimeUnit unit) {
         this.unit = unit;
         return this;
@@ -157,7 +155,7 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
                 return new Period().withYears(number);
             case UNLIMITED:
             default:
-                throw new  IllegalStateException("Unexpected duration unit " + unit);
+                throw new IllegalStateException("Unexpected duration unit " + unit);
         }
     }
 
@@ -188,4 +186,19 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
         result = 31 * result + (number != null ? number.hashCode() : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeBoolean(unit != null);
+        if (unit != null) {
+            out.writeUTF(unit.name());
+        }
+        out.writeInt(number);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.unit = in.readBoolean() ? TimeUnit.valueOf(in.readUTF()) : null;
+        this.number = in.readInt();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
index 924fbf5..493505f 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
@@ -1,5 +1,6 @@
 /*
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -16,6 +17,10 @@
 
 package org.killbill.billing.catalog;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.net.URI;
 
 import javax.xml.bind.annotation.XmlAccessType;
@@ -31,7 +36,7 @@ import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultFixed extends ValidatingConfig<StandaloneCatalog> implements Fixed {
+public class DefaultFixed extends ValidatingConfig<StandaloneCatalog> implements Fixed, Externalizable {
 
     @XmlAttribute(required = false)
     private FixedType type;
@@ -49,6 +54,7 @@ public class DefaultFixed extends ValidatingConfig<StandaloneCatalog> implements
         return fixedPrice;
     }
 
+    // Required for deserialization
     public DefaultFixed() {
     }
 
@@ -58,11 +64,11 @@ public class DefaultFixed extends ValidatingConfig<StandaloneCatalog> implements
     }
 
     @Override
-    public void initialize(final StandaloneCatalog root, final URI uri) {
-        super.initialize(root, uri);
+    public void initialize(final StandaloneCatalog root) {
+        super.initialize(root);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
         if (fixedPrice != null) {
-            fixedPrice.initialize(root, uri);
+            fixedPrice.initialize(root);
         }
     }
 
@@ -112,4 +118,19 @@ public class DefaultFixed extends ValidatingConfig<StandaloneCatalog> implements
         result = 31 * result + (fixedPrice != null ? fixedPrice.hashCode() : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeBoolean(type != null);
+        if (type != null) {
+            out.writeUTF(type.name());
+        }
+        out.writeObject(fixedPrice);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.type = in.readBoolean() ? FixedType.valueOf(in.readUTF()) : null;
+        this.fixedPrice = (DefaultInternationalPrice) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
index d2b5f7c..663579d 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,8 +18,11 @@
 
 package org.killbill.billing.catalog;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.math.BigDecimal;
-import java.net.URI;
 import java.util.Arrays;
 
 import javax.xml.bind.annotation.XmlAccessType;
@@ -35,21 +40,18 @@ import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalog> implements InternationalPrice {
+public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalog> implements InternationalPrice, Externalizable {
 
     // No prices is a zero cost plan in all currencies
     @XmlElement(name = "price", required = false)
     private DefaultPrice[] prices;
 
-
-    /* (non-Javadoc)
-      * @see org.killbill.billing.catalog.InternationalPrice#getPrices()
-      */
     @Override
     public Price[] getPrices() {
         return prices;
     }
 
+    // Required for deserialization
     public DefaultInternationalPrice() {}
 
     public DefaultInternationalPrice(final DefaultInternationalPrice in, final PlanPhasePriceOverride override, final boolean fixed) {
@@ -61,7 +63,7 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
             this.prices = new DefaultPrice[in.getPrices().length];
             // There is a question on whether we keep the other prices that were not overridden or only have one entry for the overridden price on that currency.
             for (int i = 0; i < in.getPrices().length; i++) {
-                final DefaultPrice curPrice = (DefaultPrice)  in.getPrices()[i];
+                final DefaultPrice curPrice = (DefaultPrice) in.getPrices()[i];
                 if (curPrice.getCurrency().equals(override.getCurrency())) {
                     prices[i] = new DefaultPrice(fixed ? override.getFixedPrice() : override.getRecurringPrice(), override.getCurrency());
                 } else {
@@ -74,8 +76,8 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
     public DefaultInternationalPrice(final DefaultInternationalPrice in, final BigDecimal overriddenPrice, final Currency currency) {
         this.prices = in.getPrices() != null ? new DefaultPrice[in.getPrices().length] : null;
         for (int i = 0; i < in.getPrices().length; i++) {
-            final DefaultPrice curPrice = (DefaultPrice)  in.getPrices()[i];
-            if (curPrice.getCurrency().equals(currency)){
+            final DefaultPrice curPrice = (DefaultPrice) in.getPrices()[i];
+            if (curPrice.getCurrency().equals(currency)) {
                 prices[i] = new DefaultPrice(overriddenPrice, currency);
             } else {
                 prices[i] = curPrice;
@@ -83,9 +85,6 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
         }
     }
 
-    /* (non-Javadoc)
-      * @see org.killbill.billing.catalog.IInternationalPrice#getPrice(org.killbill.billing.catalog.api.Currency)
-      */
     @Override
     public BigDecimal getPrice(final Currency currency) throws CatalogApiException {
         if (prices.length == 0) {
@@ -105,18 +104,17 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
         return this;
     }
 
-
     @Override
     public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {
         final Currency[] supportedCurrencies = catalog.getCurrentSupportedCurrencies();
         for (final Price p : prices) {
             final Currency currency = p.getCurrency();
             if (!currencyIsSupported(currency, supportedCurrencies)) {
-                errors.add("Unsupported currency: " + currency, catalog.getCatalogURI(), this.getClass(), "");
+                errors.add("Unsupported currency: " + currency, this.getClass(), "");
             }
             try {
                 if (p.getValue().doubleValue() < 0.0) {
-                    errors.add("Negative value for price in currency: " + currency, catalog.getCatalogURI(), this.getClass(), "");
+                    errors.add("Negative value for price in currency: " + currency, this.getClass(), "");
                 }
             } catch (CurrencyValueNull e) {
                 // No currency => nothing to check, ignore exception
@@ -134,14 +132,12 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
         return false;
     }
 
-
     @Override
-    public void initialize(final StandaloneCatalog root, final URI uri) {
-        super.initialize(root, uri);
+    public void initialize(final StandaloneCatalog root) {
+        super.initialize(root);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
     }
 
-
     @Override
     public boolean isZero() {
         for (final DefaultPrice price : prices) {
@@ -178,4 +174,14 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
     public int hashCode() {
         return prices != null ? Arrays.hashCode(prices) : 0;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeObject(prices);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.prices = (DefaultPrice[]) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
index 084b644..e526dc8 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
@@ -1,7 +1,7 @@
 /*
- * Copyright 2010-2011 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,7 +18,10 @@
 
 package org.killbill.billing.catalog;
 
-import java.net.URI;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -31,7 +34,7 @@ import org.killbill.xmlloader.ValidationError;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements Limit {
+public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements Limit, Externalizable {
 
     @XmlElement(required = true)
     @XmlIDREF
@@ -43,25 +46,16 @@ public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements
     @XmlElement(required = false)
     private Double min;
 
-    /* (non-Javadoc)
-     * @see org.killbill.billing.catalog.Limit#getUnit()
-     */
     @Override
     public DefaultUnit getUnit() {
         return unit;
     }
 
-    /* (non-Javadoc)
-     * @see org.killbill.billing.catalog.Limit#getMax()
-     */
     @Override
     public Double getMax() {
         return max;
     }
 
-    /* (non-Javadoc)
-     * @see org.killbill.billing.catalog.Limit#getMin()
-     */
     @Override
     public Double getMin() {
         return min;
@@ -71,19 +65,18 @@ public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements
     public ValidationErrors validate(StandaloneCatalog root, ValidationErrors errors) {
         if (!CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE.equals(max) &&
             !CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE.equals(min) &&
-                   max.doubleValue() < min.doubleValue()) {
-            errors.add(new ValidationError("max must be greater than min", root.getCatalogURI(), Limit.class, ""));
+            max.doubleValue() < min.doubleValue()) {
+            errors.add(new ValidationError("max must be greater than min", Limit.class, ""));
         }
         return errors;
     }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
     }
 
-
     @Override
     public boolean compliesWith(double value) {
         if (!CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE.equals(max)) {
@@ -145,4 +138,18 @@ public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements
         result = 31 * result + min.hashCode();
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeObject(unit);
+        out.writeDouble(max);
+        out.writeDouble(min);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.unit = (DefaultUnit) in.readObject();
+        this.max = in.readDouble();
+        this.min = in.readDouble();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultMutableStaticCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultMutableStaticCatalog.java
index c6d9a6c..9d111f6 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultMutableStaticCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultMutableStaticCatalog.java
@@ -50,7 +50,7 @@ public class DefaultMutableStaticCatalog extends StandaloneCatalog implements Mu
             .setPlans(input.getCurrentPlans())
             .setPlanRules(input.getPlanRules())
             .setPriceLists(input.getPriceLists());
-        initialize(this, null);
+        initialize(this);
     }
 
     @Override
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
index d248450..ad332e6 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -22,7 +22,6 @@ import java.io.Externalizable;
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
-import java.net.URI;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -37,6 +36,7 @@ import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlID;
 import javax.xml.bind.annotation.XmlIDREF;
+import javax.xml.bind.annotation.XmlRootElement;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.ErrorCode;
@@ -52,15 +52,13 @@ import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.Recurring;
 import org.killbill.billing.catalog.api.StaticCatalog;
 import org.killbill.billing.catalog.api.TimeUnit;
-import org.killbill.billing.util.cache.ExternalizableInput;
-import org.killbill.billing.util.cache.ExternalizableOutput;
-import org.killbill.billing.util.cache.MapperHolder;
 import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationError;
 import org.killbill.xmlloader.ValidationErrors;
 
 import com.google.common.annotations.VisibleForTesting;
 
+@XmlRootElement(name = "plans")
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements Plan, Externalizable {
 
@@ -106,7 +104,6 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
 
     // For deserialization
     public DefaultPlan() {
-        initialPhases = new DefaultPlanPhase[0];
     }
 
     public DefaultPlan(final StandaloneCatalog staticCatalog) {
@@ -217,8 +214,8 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
     }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
 
         if (prettyName == null) {
@@ -226,11 +223,11 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
         }
         if (finalPhase != null) {
             finalPhase.setPlan(this);
-            finalPhase.initialize(catalog, sourceURI);
+            finalPhase.initialize(catalog);
         }
         for (final DefaultPlanPhase p : initialPhases) {
             p.setPlan(this);
-            p.initialize(catalog, sourceURI);
+            p.initialize(catalog);
         }
         if (recurringBillingMode == null) {
             this.recurringBillingMode = catalog.getRecurringBillingMode();
@@ -247,15 +244,15 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
             errors.add(new ValidationError(String.format("Price effective date %s is before catalog effective date '%s'",
                                                          effectiveDateForExistingSubscriptions,
                                                          catalog.getEffectiveDate()),
-                                           catalog.getCatalogURI(), DefaultPlan.class, ""));
+                                           DefaultPlan.class, ""));
         }
 
         if (recurringBillingMode == null) {
-            errors.add(new ValidationError(String.format("Invalid reccuring billingMode for plan '%s'", name), catalog.getCatalogURI(), DefaultPlan.class, ""));
+            errors.add(new ValidationError(String.format("Invalid recurring billingMode for plan '%s'", name), DefaultPlan.class, ""));
         }
 
         if (product == null) {
-            errors.add(new ValidationError(String.format("Invalid product for plan '%s'", name), catalog.getCatalogURI(), DefaultPlan.class, ""));
+            errors.add(new ValidationError(String.format("Invalid product for plan '%s'", name), DefaultPlan.class, ""));
         }
 
         for (final DefaultPlanPhase cur : initialPhases) {
@@ -263,7 +260,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
             if (cur.getPhaseType() == PhaseType.EVERGREEN) {
                 errors.add(new ValidationError(String.format("Initial Phase %s of plan %s cannot be of type %s",
                                                              cur.getName(), name, cur.getPhaseType()),
-                                               catalog.getCatalogURI(), DefaultPlan.class, ""));
+                                               DefaultPlan.class, ""));
             }
         }
 
@@ -272,7 +269,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
         if (finalPhase.getPhaseType() == PhaseType.TRIAL || finalPhase.getPhaseType() == PhaseType.DISCOUNT) {
             errors.add(new ValidationError(String.format("Final Phase %s of plan %s cannot be of type %s",
                                                          finalPhase.getName(), name, finalPhase.getPhaseType()),
-                                           catalog.getCatalogURI(), DefaultPlan.class, ""));
+                                           DefaultPlan.class, ""));
         }
         // Safety check
         if (plansAllowedInBundle == null) {
@@ -413,12 +410,31 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
     }
 
     @Override
-    public void readExternal(final ObjectInput in) throws IOException {
-        MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.name = in.readUTF();
+        this.prettyName = in.readUTF();
+        this.effectiveDateForExistingSubscriptions = (Date) in.readObject();
+        this.product = (DefaultProduct) in.readObject();
+        this.recurringBillingMode = in.readBoolean() ? BillingMode.valueOf(in.readUTF()) : null;
+        this.initialPhases = (DefaultPlanPhase[]) in.readObject();
+        this.finalPhase = (DefaultPlanPhase) in.readObject();
+        this.plansAllowedInBundle = in.readInt();
+        this.priceListName = in.readUTF();
     }
 
     @Override
     public void writeExternal(final ObjectOutput oo) throws IOException {
-        MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+        oo.writeUTF(name);
+        oo.writeUTF(prettyName);
+        oo.writeObject(effectiveDateForExistingSubscriptions);
+        oo.writeObject(product);
+        oo.writeBoolean(recurringBillingMode != null);
+        if (recurringBillingMode != null) {
+            oo.writeUTF(recurringBillingMode.name());
+        }
+        oo.writeObject(initialPhases);
+        oo.writeObject(finalPhase);
+        oo.writeInt(plansAllowedInBundle);
+        oo.writeUTF(priceListName);
     }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
index 502fa83..9081bbc 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,7 +18,10 @@
 
 package org.killbill.billing.catalog;
 
-import java.net.URI;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.util.Arrays;
 
 import javax.annotation.Nullable;
@@ -36,6 +39,7 @@ import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.Recurring;
 import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.catalog.api.UsagePriceOverride;
@@ -47,7 +51,7 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implements PlanPhase {
+public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implements PlanPhase, Externalizable {
 
     @XmlAttribute(required = false)
     private String prettyName;
@@ -69,13 +73,15 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
     private DefaultUsage[] usages;
 
     // Not exposed in XML
-    private Plan plan;
+    private String planName;
+    private Product product;
 
+    // Required for deserialization
     public DefaultPlanPhase() {
-        usages = new DefaultUsage[0];
+        this.usages = new DefaultUsage[0];
     }
 
-    public DefaultPlanPhase(final DefaultPlan parentPlan, final DefaultPlanPhase in, @Nullable final PlanPhasePriceOverride override) {
+    public DefaultPlanPhase(final Plan parentPlan, final DefaultPlanPhase in, @Nullable final PlanPhasePriceOverride override) {
         this.type = in.getPhaseType();
         this.duration = (DefaultDuration) in.getDuration();
         this.fixed = override != null && override.getFixedPrice() != null ? new DefaultFixed((DefaultFixed) in.getFixed(), override) : (DefaultFixed) in.getFixed();
@@ -83,20 +89,20 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
         this.usages = new DefaultUsage[in.getUsages().length];
         for (int i = 0; i < in.getUsages().length; i++) {
             final Usage curUsage = in.getUsages()[i];
-            if(override != null && override.getUsagePriceOverrides()!= null) {
+            if (override != null && override.getUsagePriceOverrides() != null) {
                 final UsagePriceOverride usagePriceOverride = Iterables.tryFind(override.getUsagePriceOverrides(), new Predicate<UsagePriceOverride>() {
-                 @Override
-                 public boolean apply(final UsagePriceOverride input) {
-                     return input !=null && input.getName().equals(curUsage.getName());
-                 }
-                 }).orNull();
-                usages[i] = (usagePriceOverride !=null) ? new DefaultUsage(in.getUsages()[i], usagePriceOverride, override.getCurrency()) : (DefaultUsage)curUsage;
-            }
-            else {
-                usages[i] = (DefaultUsage)curUsage;
+                    @Override
+                    public boolean apply(final UsagePriceOverride input) {
+                        return input != null && input.getName().equals(curUsage.getName());
+                    }
+                }).orNull();
+                usages[i] = (usagePriceOverride != null) ? new DefaultUsage(in.getUsages()[i], usagePriceOverride, override.getCurrency()) : (DefaultUsage) curUsage;
+            } else {
+                usages[i] = (DefaultUsage) curUsage;
             }
         }
-        this.plan = parentPlan;
+        this.planName = parentPlan.getName();
+        this.product = parentPlan.getProduct();
     }
 
     public static String phaseName(final String planName, final PhaseType phasetype) {
@@ -126,7 +132,7 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
             }
         }
         // Second, check if there are limits defined at the product section.
-        return plan.getProduct().compliesWithLimits(unit, value);
+        return product.compliesWithLimits(unit, value);
     }
 
     @Override
@@ -146,7 +152,7 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
 
     @Override
     public String getName() {
-        return phaseName(plan.getName(), this.getPhaseType());
+        return phaseName(planName, this.getPhaseType());
     }
 
     @Override
@@ -161,15 +167,14 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
 
     @Override
     public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {
-
-        if (plan == null) {
-            errors.add(new ValidationError(String.format("Invalid plan for phase '%s'", type), catalog.getCatalogURI(), DefaultPlanPhase.class, ""));
+        if (planName == null) {
+            errors.add(new ValidationError(String.format("Invalid plan for phase '%s'", type), DefaultPlanPhase.class, ""));
         }
 
         if (fixed == null && recurring == null && usages.length == 0) {
             errors.add(new ValidationError(String.format("Phase %s of plan %s need to define at least either a fixed or recurrring or usage section.",
-                                                         type.toString(), plan.getName()),
-                                           catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
+                                                         type.toString(), planName),
+                                           DefaultPlanPhase.class, type.toString()));
         }
         if (fixed != null) {
             fixed.validate(catalog, errors);
@@ -184,24 +189,23 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
     }
 
     @Override
-    public void initialize(final StandaloneCatalog root, final URI uri) {
-
-        super.initialize(root, uri);
+    public void initialize(final StandaloneCatalog root) {
+        super.initialize(root);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
 
         if (fixed != null) {
-            fixed.initialize(root, uri);
+            fixed.initialize(root);
         }
         if (recurring != null) {
-            recurring.initialize(root, uri);
-            recurring.setPlan(plan);
+            recurring.initialize(root);
+            recurring.setPlan(planName);
             recurring.setPhase(this);
         }
-        for (DefaultUsage usage : usages) {
-            usage.initialize(root, uri);
+        for (final DefaultUsage usage : usages) {
+            usage.initialize(root);
             usage.setPhase(this);
         }
-        duration.initialize(root, uri);
+        duration.initialize(root);
     }
 
     public DefaultPlanPhase setPrettyName(final String prettyName) {
@@ -235,7 +239,8 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
     }
 
     public DefaultPlanPhase setPlan(final Plan plan) {
-        this.plan = plan;
+        this.planName = plan.getName();
+        this.product = plan.getProduct();
         return this;
     }
 
@@ -289,8 +294,38 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
         sb.append(", fixed=").append(fixed);
         sb.append(", recurring=").append(recurring);
         sb.append(", usages=").append(Arrays.toString(usages));
-        sb.append(", plan=").append(plan.getName());
+        sb.append(", plan=").append(planName);
         sb.append('}');
         return sb.toString();
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeBoolean(prettyName != null);
+        if (prettyName != null) {
+            out.writeUTF(prettyName);
+        }
+        out.writeBoolean(type != null);
+        if (type != null) {
+            out.writeUTF(type.name());
+        }
+        out.writeObject(duration);
+        out.writeObject(fixed);
+        out.writeObject(recurring);
+        out.writeObject(usages);
+        out.writeUTF(planName);
+        out.writeObject(product);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.prettyName = in.readBoolean() ? in.readUTF() : null;
+        this.type = in.readBoolean() ? PhaseType.valueOf(in.readUTF()) : null;
+        this.duration = (DefaultDuration) in.readObject();
+        this.fixed = (DefaultFixed) in.readObject();
+        this.recurring = (DefaultRecurring) in.readObject();
+        this.usages = (DefaultUsage[]) in.readObject();
+        this.planName = in.readUTF();
+        this.product = (Product) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPrice.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPrice.java
index 069ccb6..66ac47e 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPrice.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPrice.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,11 +18,16 @@
 
 package org.killbill.billing.catalog;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.math.BigDecimal;
+import java.net.URI;
+
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
-import java.math.BigDecimal;
-import java.net.URI;
 
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.CurrencyValueNull;
@@ -29,7 +36,8 @@ import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultPrice extends ValidatingConfig<StandaloneCatalog> implements Price {
+public class DefaultPrice extends ValidatingConfig<StandaloneCatalog> implements Price, Externalizable {
+
     @XmlElement(required = true)
     private Currency currency;
 
@@ -46,17 +54,11 @@ public class DefaultPrice extends ValidatingConfig<StandaloneCatalog> implements
         this.currency = currency;
     }
 
-    /* (non-Javadoc)
-      * @see org.killbill.billing.catalog.IPrice#getCurrency()
-      */
     @Override
     public Currency getCurrency() {
         return currency;
     }
 
-    /* (non-Javadoc)
-      * @see org.killbill.billing.catalog.IPrice#getValue()
-      */
     @Override
     public BigDecimal getValue() throws CurrencyValueNull {
         if (value == null) {
@@ -81,12 +83,11 @@ public class DefaultPrice extends ValidatingConfig<StandaloneCatalog> implements
     }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
     }
 
-
     @Override
     public boolean equals(final Object o) {
         if (this == o) {
@@ -114,4 +115,19 @@ public class DefaultPrice extends ValidatingConfig<StandaloneCatalog> implements
         result = 31 * result + (value != null ? value.hashCode() : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeBoolean(currency != null);
+        if (currency != null) {
+            out.writeUTF(currency.name());
+        }
+        out.writeObject(value);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.currency = in.readBoolean() ? Currency.valueOf(in.readUTF()) : null;
+        this.value = (BigDecimal) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java
index 7e686b2..68ac129 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,6 +18,10 @@
 
 package org.killbill.billing.catalog;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -37,7 +43,7 @@ import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implements PriceList {
+public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implements PriceList, Externalizable {
 
     @XmlAttribute(required = true)
     @XmlID
@@ -48,15 +54,15 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
 
     @XmlElementWrapper(name = "plans", required = true)
     @XmlIDREF
-    @XmlElement(type=DefaultPlan.class, name = "plan", required = false)
+    @XmlElement(type = DefaultPlan.class, name = "plan", required = false)
     private CatalogEntityCollection<Plan> plans;
 
     public DefaultPriceList() {
-        this.plans = new CatalogEntityCollection();
+        this.plans = new CatalogEntityCollection<Plan>();
     }
 
     public DefaultPriceList(final DefaultPlan[] plans, final String name) {
-        this.plans = new CatalogEntityCollection(plans);
+        this.plans = new CatalogEntityCollection<Plan>(plans);
         this.name = name;
     }
 
@@ -65,13 +71,10 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
         return plans;
     }
 
-
     public CatalogEntityCollection<Plan> getCatalogEntityCollectionPlan() {
         return plans;
     }
-    /* (non-Javadoc)
-      * @see org.killbill.billing.catalog.IPriceList#getName()
-      */
+
     @Override
     public String getName() {
         return name;
@@ -82,9 +85,6 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
         return prettyName;
     }
 
-    /* (non-Javadoc)
-      * @see org.killbill.billing.catalog.IPriceList#findPlan(org.killbill.billing.catalog.api.IProduct, org.killbill.billing.catalog.api.BillingPeriod)
-      */
     @Override
     public Collection<Plan> findPlans(final Product product, final BillingPeriod period) {
         final List<Plan> result = new ArrayList<Plan>(plans.size());
@@ -106,10 +106,9 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
         return errors;
     }
 
-
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
 
         if (prettyName == null) {
@@ -159,4 +158,24 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
         return "DefaultPriceList{" +
                "name='" + name + '}';
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeBoolean(name != null);
+        if (name != null) {
+            out.writeUTF(name);
+        }
+        out.writeBoolean(prettyName != null);
+        if (prettyName != null) {
+            out.writeUTF(prettyName);
+        }
+        out.writeObject(plans);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.name = in.readBoolean() ? in.readUTF() : null;
+        this.prettyName = in.readBoolean() ? in.readUTF() : null;
+        this.plans = (CatalogEntityCollection<Plan>) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceListSet.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceListSet.java
index 4aaf3cb..6101548 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceListSet.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceListSet.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,16 +18,19 @@
 
 package org.killbill.billing.catalog;
 
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-
-import java.net.URI;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.CatalogApiException;
@@ -38,17 +43,16 @@ import org.killbill.xmlloader.ValidationError;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> implements PriceListSet {
+public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> implements PriceListSet, Externalizable {
+
     @XmlElement(required = true, name = "defaultPriceList")
     private DefaultPriceList defaultPricelist;
 
     @XmlElement(required = false, name = "childPriceList")
     private DefaultPriceList[] childPriceLists;
 
+    // Required for deserialization
     public DefaultPriceListSet() {
-        if (childPriceLists == null) {
-            childPriceLists = new DefaultPriceList[0];
-        }
     }
 
     public DefaultPriceListSet(final DefaultPriceList defaultPricelist, final DefaultPriceList[] childPriceLists) {
@@ -66,7 +70,7 @@ public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> imp
         if (plans.size() == 0) {
             plans = defaultPricelist.findPlans(product, period);
         }
-        switch(plans.size()) {
+        switch (plans.size()) {
             case 0:
                 return null;
             case 1:
@@ -99,7 +103,7 @@ public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> imp
         for (final DefaultPriceList pl : childPriceLists) {
             if (pl.getName().equals(PriceListSet.DEFAULT_PRICELIST_NAME)) {
                 errors.add(new ValidationError("Pricelists cannot use the reserved name '" + PriceListSet.DEFAULT_PRICELIST_NAME + "'",
-                                               catalog.getCatalogURI(), DefaultPriceListSet.class, pl.getName()));
+                                               DefaultPriceListSet.class, pl.getName()));
             }
             pl.validate(catalog, errors); // and validate the individual pricelists
         }
@@ -107,12 +111,11 @@ public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> imp
     }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
     }
 
-
     public DefaultPriceList getDefaultPricelist() {
         return defaultPricelist;
     }
@@ -159,4 +162,15 @@ public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> imp
         return result;
     }
 
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeObject(defaultPricelist);
+        out.writeObject(childPriceLists);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.defaultPricelist = (DefaultPriceList) in.readObject();
+        this.childPriceLists = (DefaultPriceList[]) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultProduct.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultProduct.java
index 6a95b24..c9c3fd2 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultProduct.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultProduct.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,7 +18,10 @@
 
 package org.killbill.billing.catalog;
 
-import java.net.URI;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.util.Arrays;
 import java.util.Collection;
 
@@ -36,7 +41,7 @@ import org.killbill.xmlloader.ValidationError;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implements Product {
+public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implements Product, Externalizable {
 
     @XmlAttribute(required = true)
     @XmlID
@@ -50,19 +55,19 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
 
     @XmlElementWrapper(name = "included", required = false)
     @XmlIDREF
-    @XmlElement(type=DefaultProduct.class, name = "addonProduct", required = false)
+    @XmlElement(type = DefaultProduct.class, name = "addonProduct", required = false)
     private CatalogEntityCollection<Product> included;
 
     @XmlElementWrapper(name = "available", required = false)
     @XmlIDREF
-    @XmlElement(type=DefaultProduct.class, name = "addonProduct", required = false)
+    @XmlElement(type = DefaultProduct.class, name = "addonProduct", required = false)
     private CatalogEntityCollection<Product> available;
 
     @XmlElementWrapper(name = "limits", required = false)
     @XmlElement(name = "limit", required = false)
     private DefaultLimit[] limits;
 
-    //Not included in XML
+    // Not included in XML
     private String catalogName;
 
     @Override
@@ -85,11 +90,11 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
         return available.getEntries();
     }
 
-
     public CatalogEntityCollection<Product> getCatalogEntityCollectionAvailable() {
         return available;
-    };
+    }
 
+    // Required for deserialization
     public DefaultProduct() {
         this.included = new CatalogEntityCollection<Product>();
         this.available = new CatalogEntityCollection<Product>();
@@ -136,11 +141,10 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
         return limits;
     }
 
-
     protected Limit findLimit(String unit) {
-        for (Limit limit: limits) {
-            if(limit.getUnit().getName().equals(unit) ) {
-                    return limit;
+        for (Limit limit : limits) {
+            if (limit.getUnit().getName().equals(unit)) {
+                return limit;
             }
         }
         return null;
@@ -155,14 +159,12 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
         return l.compliesWith(value);
     }
 
-
-
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
-        for (DefaultLimit cur : limits) {
-            cur.initialize(catalog, sourceURI);
+        for (final DefaultLimit cur : limits) {
+            cur.initialize(catalog);
         }
         if (prettyName == null) {
             this.prettyName = name;
@@ -173,7 +175,7 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
     @Override
     public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {
         if (catalogName == null || !catalogName.equals(catalog.getCatalogName())) {
-            errors.add(new ValidationError(String.format("Invalid catalogName for product '%s'", name), catalog.getCatalogURI(), DefaultProduct.class, ""));
+            errors.add(new ValidationError(String.format("Invalid catalogName for product '%s'", name), DefaultProduct.class, ""));
 
         }
         //TODO: MDW validation: inclusion and exclusion lists can only contain addon products
@@ -186,7 +188,7 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
         return this;
     }
 
-    public DefaultProduct setPrettyName(final String prettyName){
+    public DefaultProduct setPrettyName(final String prettyName) {
         this.prettyName = prettyName;
         return this;
     }
@@ -269,4 +271,38 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
         result = 31 * result + (catalogName != null ? catalogName.hashCode() : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeBoolean(catalogName != null);
+        if (catalogName != null) {
+            out.writeUTF(catalogName);
+        }
+        out.writeBoolean(name != null);
+        if (name != null) {
+            out.writeUTF(name);
+        }
+        out.writeBoolean(prettyName != null);
+        if (prettyName != null) {
+            out.writeUTF(prettyName);
+        }
+        out.writeBoolean(category != null);
+        if (category != null) {
+            out.writeUTF(category.name());
+        }
+        out.writeObject(included);
+        out.writeObject(available);
+        out.writeObject(limits);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.catalogName = in.readBoolean() ? in.readUTF() : null;
+        this.name = in.readBoolean() ? in.readUTF() : null;
+        this.prettyName = in.readBoolean() ? in.readUTF() : null;
+        this.category = in.readBoolean() ? ProductCategory.valueOf(in.readUTF()) : null;
+        this.included = (CatalogEntityCollection<Product>) in.readObject();
+        this.available = (CatalogEntityCollection<Product>) in.readObject();
+        this.limits = (DefaultLimit[]) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
index d4c180b..ffccb7a 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,14 +17,17 @@
 
 package org.killbill.billing.catalog;
 
-import java.net.URI;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
 
 import org.killbill.billing.catalog.api.BillingPeriod;
-import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.Recurring;
@@ -33,7 +36,7 @@ import org.killbill.xmlloader.ValidationError;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultRecurring extends ValidatingConfig<StandaloneCatalog> implements Recurring {
+public class DefaultRecurring extends ValidatingConfig<StandaloneCatalog> implements Recurring, Externalizable {
 
     @XmlElement(required = true)
     private BillingPeriod billingPeriod;
@@ -42,10 +45,11 @@ public class DefaultRecurring extends ValidatingConfig<StandaloneCatalog> implem
     private DefaultInternationalPrice recurringPrice;
 
     // Not exposed in xml.
-    private Plan plan;
-    private PlanPhase phase;
+    private String planName;
+    private PhaseType phaseType;
 
-    public DefaultRecurring() {};
+    // Required for deserialization
+    public DefaultRecurring() {}
 
     public DefaultRecurring(final DefaultRecurring in, final PlanPhasePriceOverride override) {
         this.billingPeriod = in.getBillingPeriod();
@@ -63,11 +67,11 @@ public class DefaultRecurring extends ValidatingConfig<StandaloneCatalog> implem
     }
 
     @Override
-    public void initialize(final StandaloneCatalog root, final URI uri) {
-        super.initialize(root, uri);
+    public void initialize(final StandaloneCatalog root) {
+        super.initialize(root);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
         if (recurringPrice != null) {
-            recurringPrice.initialize(root, uri);
+            recurringPrice.initialize(root);
         }
     }
 
@@ -75,31 +79,30 @@ public class DefaultRecurring extends ValidatingConfig<StandaloneCatalog> implem
     public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {
         // Validation: check for nulls
 
-        if (plan == null) {
-            errors.add(new ValidationError(String.format("Invalid plan for recurring section"), catalog.getCatalogURI(), DefaultRecurring.class, ""));
+        if (planName == null) {
+            errors.add(new ValidationError("Invalid plan for recurring section", DefaultRecurring.class, ""));
         }
 
-        if (phase == null) {
-            errors.add(new ValidationError(String.format("Invalid phase for recurring section"), catalog.getCatalogURI(), DefaultPlan.class, plan.getName().toString()));
+        if (phaseType == null) {
+            errors.add(new ValidationError("Invalid phase for recurring section", DefaultPlan.class, planName));
         }
 
-
         if (billingPeriod == null) {
-            errors.add(new ValidationError(String.format("Recurring section of Phase %s of plan %s has a recurring price but no billing period", phase.getPhaseType().toString(), plan.getName()),
-                                           catalog.getCatalogURI(), DefaultPlanPhase.class, phase.getPhaseType().toString()));
+            errors.add(new ValidationError(String.format("Recurring section of Phase %s of plan %s has a recurring price but no billing period", phaseType.toString(), planName),
+                                           DefaultPlanPhase.class, phaseType.toString()));
         }
 
         // Validation: if there is a recurring price there must be a billing period
         if ((recurringPrice != null) && (billingPeriod == null || billingPeriod == BillingPeriod.NO_BILLING_PERIOD)) {
-            errors.add(new ValidationError(String.format("Recurring section of Phase %s of plan %s has a recurring price but no billing period", phase.getPhaseType().toString(), plan.getName()),
-                                           catalog.getCatalogURI(), DefaultPlanPhase.class, phase.getPhaseType().toString()));
+            errors.add(new ValidationError(String.format("Recurring section of Phase %s of plan %s has a recurring price but no billing period", phaseType.toString(), planName),
+                                           DefaultPlanPhase.class, phaseType.toString()));
         }
 
         // Validation: if there is no recurring price there should be no billing period
         if ((recurringPrice == null) && billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
             errors.add(new ValidationError(String.format("Recurring section of Phase %s of plan %s has no recurring price but does have a billing period. The billing period should be set to '%s'",
-                                                         phase.getPhaseType().toString(), plan.getName(), BillingPeriod.NO_BILLING_PERIOD),
-                                           catalog.getCatalogURI(), DefaultPlanPhase.class, phase.getPhaseType().toString()));
+                                                         phaseType.toString(), planName, BillingPeriod.NO_BILLING_PERIOD),
+                                           DefaultPlanPhase.class, phaseType.toString()));
         }
         return errors;
     }
@@ -114,13 +117,13 @@ public class DefaultRecurring extends ValidatingConfig<StandaloneCatalog> implem
         return this;
     }
 
-    public DefaultRecurring setPlan(final Plan plan) {
-        this.plan = plan;
+    public DefaultRecurring setPlan(final String planName) {
+        this.planName = planName;
         return this;
     }
 
     public DefaultRecurring setPhase(final PlanPhase phase) {
-        this.phase = phase;
+        this.phaseType = phase.getPhaseType();
         return this;
     }
 
@@ -151,4 +154,26 @@ public class DefaultRecurring extends ValidatingConfig<StandaloneCatalog> implem
         result = 31 * result + (recurringPrice != null ? recurringPrice.hashCode() : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeBoolean(billingPeriod != null);
+        if (billingPeriod != null) {
+            out.writeUTF(billingPeriod.name());
+        }
+        out.writeObject(recurringPrice);
+        out.writeUTF(planName);
+        out.writeBoolean(phaseType != null);
+        if (phaseType != null) {
+            out.writeUTF(phaseType.name());
+        }
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.billingPeriod = in.readBoolean() ? BillingPeriod.valueOf(in.readUTF()) : null;
+        this.recurringPrice = (DefaultInternationalPrice) in.readObject();
+        this.planName = in.readUTF();
+        this.phaseType = in.readBoolean() ? PhaseType.valueOf(in.readUTF()) : null;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java
index e6c3bf3..aee9962 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,7 +17,10 @@
 
 package org.killbill.billing.catalog;
 
-import java.net.URI;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.util.Arrays;
 
 import javax.xml.bind.annotation.XmlAccessType;
@@ -41,10 +44,8 @@ import org.killbill.xmlloader.ValidationErrors;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 
-
-
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultTier extends ValidatingConfig<StandaloneCatalog> implements Tier {
+public class DefaultTier extends ValidatingConfig<StandaloneCatalog> implements Tier, Externalizable {
 
     @XmlElementWrapper(name = "limits", required = false)
     @XmlElement(name = "limit", required = false)
@@ -67,17 +68,16 @@ public class DefaultTier extends ValidatingConfig<StandaloneCatalog> implements 
     private UsageType usageType;
     private PlanPhase phase;
 
+    // Required for deserialization
     public DefaultTier() {
-        limits = new DefaultLimit[0];
-        blocks = new DefaultTieredBlock[0];
     }
 
     public DefaultTier(Tier in, TierPriceOverride override, Currency currency) {
-        this.limits = (DefaultLimit[])in.getLimits();
+        this.limits = (DefaultLimit[]) in.getLimits();
         this.blocks = new DefaultTieredBlock[in.getTieredBlocks().length];
 
         for (int i = 0; i < in.getTieredBlocks().length; i++) {
-            if(override != null && override.getTieredBlockPriceOverrides() != null) {
+            if (override != null && override.getTieredBlockPriceOverrides() != null) {
                 final TieredBlock curTieredBlock = in.getTieredBlocks()[i];
                 final TieredBlockPriceOverride overriddenTierBlock = Iterables.tryFind(override.getTieredBlockPriceOverrides(), new Predicate<TieredBlockPriceOverride>() {
                     @Override
@@ -89,9 +89,8 @@ public class DefaultTier extends ValidatingConfig<StandaloneCatalog> implements 
 
                 }).orNull();
                 blocks[i] = (overriddenTierBlock != null) ? new DefaultTieredBlock(in.getTieredBlocks()[i], overriddenTierBlock, currency) :
-                        (DefaultTieredBlock) in.getTieredBlocks()[i];
-            }
-            else {
+                            (DefaultTieredBlock) in.getTieredBlocks()[i];
+            } else {
                 blocks[i] = (DefaultTieredBlock) in.getTieredBlocks()[i];
             }
         }
@@ -154,30 +153,28 @@ public class DefaultTier extends ValidatingConfig<StandaloneCatalog> implements 
     public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {
         if (billingMode == BillingMode.IN_ARREAR && usageType == UsageType.CAPACITY && limits.length == 0) {
             errors.add(new ValidationError(String.format("Usage [IN_ARREAR CAPACITY] section of phase %s needs to define some limits",
-                                                         phase.getName()), catalog.getCatalogURI(), DefaultUsage.class, ""));
+                                                         phase.getName()), DefaultUsage.class, ""));
         }
         if (billingMode == BillingMode.IN_ARREAR && usageType == UsageType.CONSUMABLE && blocks.length == 0) {
             errors.add(new ValidationError(String.format("Usage [IN_ARREAR CONSUMABLE] section of phase %s needs to define some blocks",
-                                                         phase.getName()), catalog.getCatalogURI(), DefaultUsage.class, ""));
+                                                         phase.getName()), DefaultUsage.class, ""));
         }
         validateCollection(catalog, errors, limits);
         return errors;
     }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
         for (DefaultLimit cur : limits) {
-            cur.initialize(catalog, sourceURI);
+            cur.initialize(catalog);
         }
         for (DefaultBlock cur : blocks) {
-            cur.initialize(catalog, sourceURI);
+            cur.initialize(catalog);
         }
     }
 
-
-
     @Override
     public boolean equals(final Object o) {
         if (this == o) {
@@ -222,4 +219,20 @@ public class DefaultTier extends ValidatingConfig<StandaloneCatalog> implements 
         result = 31 * result + (recurringPrice != null ? recurringPrice.hashCode() : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeObject(limits);
+        out.writeObject(blocks);
+        out.writeObject(fixedPrice);
+        out.writeObject(recurringPrice);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.limits = (DefaultLimit[]) in.readObject();
+        this.blocks = (DefaultTieredBlock[]) in.readObject();
+        this.fixedPrice = (DefaultInternationalPrice) in.readObject();
+        this.recurringPrice = (DefaultInternationalPrice) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultTieredBlock.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultTieredBlock.java
index aa661ab..60abfaf 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultTieredBlock.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultTieredBlock.java
@@ -1,7 +1,8 @@
 /*
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,6 +17,11 @@
 
 package org.killbill.billing.catalog;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
@@ -26,7 +32,7 @@ import org.killbill.billing.catalog.api.TieredBlock;
 import org.killbill.billing.catalog.api.TieredBlockPriceOverride;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultTieredBlock extends DefaultBlock implements TieredBlock {
+public class DefaultTieredBlock extends DefaultBlock implements TieredBlock, Externalizable {
 
     @XmlElement(required = true)
     private Double max;
@@ -41,11 +47,12 @@ public class DefaultTieredBlock extends DefaultBlock implements TieredBlock {
         return this;
     }
 
+    // Required for deserialization
     public DefaultTieredBlock() {
     }
 
     public DefaultTieredBlock(TieredBlock in, TieredBlockPriceOverride override, Currency currency) {
-        super((DefaultUnit)in.getUnit(), in.getSize(),(DefaultInternationalPrice)in.getPrice(), override.getPrice(),currency);
+        super((DefaultUnit) in.getUnit(), in.getSize(), (DefaultInternationalPrice) in.getPrice(), override.getPrice(), currency);
         this.max = in.getMax();
     }
 
@@ -87,4 +94,16 @@ public class DefaultTieredBlock extends DefaultBlock implements TieredBlock {
         result = 31 * result + (max != null ? max.hashCode() : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        super.writeExternal(out);
+        out.writeDouble(max);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        super.readExternal(in);
+        this.max = in.readDouble();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java
index b3ad882..6bd7330 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,7 +18,10 @@
 
 package org.killbill.billing.catalog;
 
-import java.net.URI;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -28,7 +33,7 @@ import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultUnit extends ValidatingConfig<StandaloneCatalog> implements Unit {
+public class DefaultUnit extends ValidatingConfig<StandaloneCatalog> implements Unit, Externalizable {
 
     @XmlAttribute(required = true)
     @XmlID
@@ -37,9 +42,6 @@ public class DefaultUnit extends ValidatingConfig<StandaloneCatalog> implements 
     @XmlAttribute(required = false)
     private String prettyName;
 
-    /* (non-Javadoc)
-     * @see org.killbill.billing.catalog.Unit#getName()
-     */
     @Override
     public String getName() {
         return name;
@@ -55,10 +57,13 @@ public class DefaultUnit extends ValidatingConfig<StandaloneCatalog> implements 
         return errors;
     }
 
+    // Required for deserialization
+    public DefaultUnit() {
+    }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
         if (prettyName == null) {
             this.prettyName = name;
@@ -97,4 +102,16 @@ public class DefaultUnit extends ValidatingConfig<StandaloneCatalog> implements 
     public int hashCode() {
         return name != null ? name.hashCode() : 0;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeUTF(name);
+        out.writeUTF(prettyName);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.name = in.readUTF();
+        this.prettyName = in.readUTF();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java
index ce2a92a..86a8376 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,7 +17,10 @@
 
 package org.killbill.billing.catalog;
 
-import java.net.URI;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.util.Arrays;
 import java.util.List;
 
@@ -36,14 +39,13 @@ import org.killbill.billing.catalog.api.InternationalPrice;
 import org.killbill.billing.catalog.api.Limit;
 import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.Tier;
+import org.killbill.billing.catalog.api.TierBlockPolicy;
 import org.killbill.billing.catalog.api.TierPriceOverride;
 import org.killbill.billing.catalog.api.TieredBlock;
 import org.killbill.billing.catalog.api.TieredBlockPriceOverride;
 import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.catalog.api.UsagePriceOverride;
 import org.killbill.billing.catalog.api.UsageType;
-import org.killbill.billing.catalog.api.TierBlockPolicy;
-
 import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationError;
 import org.killbill.xmlloader.ValidationErrors;
@@ -51,9 +53,8 @@ import org.killbill.xmlloader.ValidationErrors;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 
-
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements Usage {
+public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements Usage, Externalizable {
 
     @XmlAttribute(required = true)
     @XmlID
@@ -97,54 +98,51 @@ public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements
     // Not exposed in xml.
     private PlanPhase phase;
 
+    // Required for deserialization
     public DefaultUsage() {
-        limits = new DefaultLimit[0];
-        blocks = new DefaultBlock[0];
-        tiers = new DefaultTier[0];
     }
 
     public DefaultUsage(final Usage in, UsagePriceOverride override, Currency currency) {
-              this.name = in.getName();
-              this.usageType = in.getUsageType();
-              this.tierBlockPolicy = in.getTierBlockPolicy();
-              this.billingPeriod = in.getBillingPeriod();
-              this.billingMode = in.getBillingMode();
-              this.limits = (DefaultLimit[]) in.getLimits();
-              this.blocks = (DefaultBlock[]) in.getBlocks();
-              this.tiers = new DefaultTier[in.getTiers().length];
-
-              for (int i = 0; i < in.getTiers().length; i++) {
-                  if(override != null && override.getTierPriceOverrides()!=null) {
-                      final TieredBlock[] curTieredBlocks = in.getTiers()[i].getTieredBlocks();
-
-                      final TierPriceOverride overriddenTier = Iterables.tryFind(override.getTierPriceOverrides(), new Predicate<TierPriceOverride>() {
-                          @Override
-                          public boolean apply(final TierPriceOverride input) {
-
-                            if(input !=null) {
-                              final List<TieredBlockPriceOverride> blockPriceOverrides = input.getTieredBlockPriceOverrides();
-                              for (TieredBlockPriceOverride blockDef : blockPriceOverrides) {
-                                 String unitName = blockDef.getUnitName();
-                                 Double max = blockDef.getMax();
-                                 Double size = blockDef.getSize();
-                                 for (TieredBlock curTieredBlock : curTieredBlocks) {
-                                     if (unitName.equals(curTieredBlock.getUnit().getName()) &&
-                                             Double.compare(size, curTieredBlock.getSize()) == 0 &&
-                                             Double.compare(max, curTieredBlock.getMax()) == 0) {
-                                         return true;
-                                     }
-                                 }
-                               }
+        this.name = in.getName();
+        this.usageType = in.getUsageType();
+        this.tierBlockPolicy = in.getTierBlockPolicy();
+        this.billingPeriod = in.getBillingPeriod();
+        this.billingMode = in.getBillingMode();
+        this.limits = (DefaultLimit[]) in.getLimits();
+        this.blocks = (DefaultBlock[]) in.getBlocks();
+        this.tiers = new DefaultTier[in.getTiers().length];
+
+        for (int i = 0; i < in.getTiers().length; i++) {
+            if (override != null && override.getTierPriceOverrides() != null) {
+                final TieredBlock[] curTieredBlocks = in.getTiers()[i].getTieredBlocks();
+
+                final TierPriceOverride overriddenTier = Iterables.tryFind(override.getTierPriceOverrides(), new Predicate<TierPriceOverride>() {
+                    @Override
+                    public boolean apply(final TierPriceOverride input) {
+
+                        if (input != null) {
+                            final List<TieredBlockPriceOverride> blockPriceOverrides = input.getTieredBlockPriceOverrides();
+                            for (TieredBlockPriceOverride blockDef : blockPriceOverrides) {
+                                String unitName = blockDef.getUnitName();
+                                Double max = blockDef.getMax();
+                                Double size = blockDef.getSize();
+                                for (TieredBlock curTieredBlock : curTieredBlocks) {
+                                    if (unitName.equals(curTieredBlock.getUnit().getName()) &&
+                                        Double.compare(size, curTieredBlock.getSize()) == 0 &&
+                                        Double.compare(max, curTieredBlock.getMax()) == 0) {
+                                        return true;
+                                    }
+                                }
                             }
-                            return false;
-                          }
-                      }).orNull();
-                      tiers[i] = (overriddenTier != null) ? new DefaultTier(in.getTiers()[i], overriddenTier, currency) : (DefaultTier)in.getTiers()[i] ;
-                  }
-                  else {
-                      tiers[i] = (DefaultTier) in.getTiers()[i];
-                  }
-              }
+                        }
+                        return false;
+                    }
+                }).orNull();
+                tiers[i] = (overriddenTier != null) ? new DefaultTier(in.getTiers()[i], overriddenTier, currency) : (DefaultTier) in.getTiers()[i];
+            } else {
+                tiers[i] = (DefaultTier) in.getTiers()[i];
+            }
+        }
     }
 
     @Override
@@ -215,16 +213,16 @@ public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements
     public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {
         if (billingMode == BillingMode.IN_ADVANCE && usageType == UsageType.CAPACITY && limits.length == 0) {
             errors.add(new ValidationError(String.format("Usage [IN_ADVANCE CAPACITY] section of phase %s needs to define some limits",
-                                                         phase.toString()), catalog.getCatalogURI(), DefaultUsage.class, ""));
+                                                         phase.toString()), DefaultUsage.class, ""));
         }
         if (billingMode == BillingMode.IN_ADVANCE && usageType == UsageType.CONSUMABLE && blocks.length == 0) {
             errors.add(new ValidationError(String.format("Usage [IN_ADVANCE CONSUMABLE] section of phase %s needs to define some blocks",
-                                                         phase.toString()), catalog.getCatalogURI(), DefaultUsage.class, ""));
+                                                         phase.toString()), DefaultUsage.class, ""));
         }
 
         if (billingMode == BillingMode.IN_ARREAR && tiers.length == 0) {
             errors.add(new ValidationError(String.format("Usage [IN_ARREAR] section of phase %s needs to define some tiers",
-                                                         phase.toString()), catalog.getCatalogURI(), DefaultUsage.class, ""));
+                                                         phase.toString()), DefaultUsage.class, ""));
         }
         validateCollection(catalog, errors, limits);
         validateCollection(catalog, errors, tiers);
@@ -232,8 +230,8 @@ public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements
     }
 
     @Override
-    public void initialize(final StandaloneCatalog root, final URI uri) {
-        super.initialize(root, uri);
+    public void initialize(final StandaloneCatalog root) {
+        super.initialize(root);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
 
         if (prettyName == null) {
@@ -241,15 +239,15 @@ public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements
         }
 
         for (DefaultLimit limit : limits) {
-            limit.initialize(root, uri);
+            limit.initialize(root);
         }
         for (DefaultBlock block : blocks) {
-            block.initialize(root, uri);
+            block.initialize(root);
             block.setPhase(phase);
         }
 
         for (DefaultTier tier : tiers) {
-            tier.initialize(root, uri);
+            tier.initialize(root);
             tier.setPhase(phase);
         }
     }
@@ -380,4 +378,46 @@ public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements
         result = 31 * result + (recurringPrice != null ? recurringPrice.hashCode() : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeUTF(name);
+        out.writeUTF(prettyName);
+        out.writeBoolean(billingMode != null);
+        if (billingMode != null) {
+            out.writeUTF(billingMode.name());
+        }
+        out.writeBoolean(usageType != null);
+        if (usageType != null) {
+            out.writeUTF(usageType.name());
+        }
+        out.writeBoolean(tierBlockPolicy != null);
+        if (tierBlockPolicy != null) {
+            out.writeUTF(tierBlockPolicy.name());
+        }
+        out.writeBoolean(billingPeriod != null);
+        if (billingPeriod != null) {
+            out.writeUTF(billingPeriod.name());
+        }
+        out.writeObject(limits);
+        out.writeObject(blocks);
+        out.writeObject(tiers);
+        out.writeObject(fixedPrice);
+        out.writeObject(recurringPrice);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.name = in.readUTF();
+        this.prettyName = in.readUTF();
+        this.billingMode = in.readBoolean() ? BillingMode.valueOf(in.readUTF()) : null;
+        this.usageType = in.readBoolean() ? UsageType.valueOf(in.readUTF()) : null;
+        this.tierBlockPolicy = in.readBoolean() ? TierBlockPolicy.valueOf(in.readUTF()) : null;
+        this.billingPeriod = in.readBoolean() ? BillingPeriod.valueOf(in.readUTF()) : null;
+        this.limits = (DefaultLimit[]) in.readObject();
+        this.blocks = (DefaultBlock[]) in.readObject();
+        this.tiers = (DefaultTier[]) in.readObject();
+        this.fixedPrice = (DefaultInternationalPrice) in.readObject();
+        this.recurringPrice = (DefaultInternationalPrice) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultVersionedCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultVersionedCatalog.java
index 83e485b..5edf0dc 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultVersionedCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultVersionedCatalog.java
@@ -22,7 +22,6 @@ import java.io.Externalizable;
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
-import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -43,7 +42,6 @@ import org.joda.time.DateTime;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingAlignment;
-import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.Listing;
@@ -60,9 +58,6 @@ import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.StaticCatalog;
 import org.killbill.billing.catalog.api.Unit;
 import org.killbill.billing.catalog.api.VersionedCatalog;
-import org.killbill.billing.util.cache.ExternalizableInput;
-import org.killbill.billing.util.cache.ExternalizableOutput;
-import org.killbill.billing.util.cache.MapperHolder;
 import org.killbill.clock.Clock;
 import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationError;
@@ -74,7 +69,7 @@ public class DefaultVersionedCatalog extends ValidatingConfig<DefaultVersionedCa
 
     private static final long serialVersionUID = 3181874902672322725L;
 
-    private final Clock clock;
+    private Clock clock;
 
     @XmlElementWrapper(name = "versions", required = true)
     @XmlElement(name = "version", required = true)
@@ -335,13 +330,13 @@ public class DefaultVersionedCatalog extends ValidatingConfig<DefaultVersionedCa
     }
 
     @Override
-    public void initialize(final DefaultVersionedCatalog catalog, final URI sourceURI) {
+    public void initialize(final DefaultVersionedCatalog catalog) {
         //
         // Initialization is performed first on each StandaloneCatalog (XMLLoader#initializeAndValidate)
         // and then later on the VersionedCatalog, so we only initialize and validate VersionedCatalog
         // *without** recursively through each StandaloneCatalog
         //
-        super.initialize(catalog, sourceURI);
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
     }
 
@@ -352,13 +347,13 @@ public class DefaultVersionedCatalog extends ValidatingConfig<DefaultVersionedCa
         for (final StandaloneCatalog c : versions) {
             if (effectiveDates.contains(c.getEffectiveDate())) {
                 errors.add(new ValidationError(String.format("Catalog effective date '%s' already exists for a previous version", c.getEffectiveDate()),
-                                               c.getCatalogURI(), VersionedCatalog.class, ""));
+                                               VersionedCatalog.class, ""));
             } else {
                 effectiveDates.add(c.getEffectiveDate());
             }
             if (!c.getCatalogName().equals(catalogName)) {
                 errors.add(new ValidationError(String.format("Catalog name '%s' is not consistent across versions ", c.getCatalogName()),
-                                               c.getCatalogURI(), VersionedCatalog.class, ""));
+                                               VersionedCatalog.class, ""));
             }
             errors.addAll(c.validate(c, errors));
         }
@@ -459,13 +454,48 @@ public class DefaultVersionedCatalog extends ValidatingConfig<DefaultVersionedCa
     }
 
     @Override
-    public void readExternal(final ObjectInput in) throws IOException {
-        MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.catalogName = in.readBoolean() ? in.readUTF() : null;
+        this.versions.addAll((Collection<? extends StandaloneCatalog>) in.readObject());
     }
 
     @Override
     public void writeExternal(final ObjectOutput oo) throws IOException {
-        MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+        oo.writeBoolean(catalogName != null);
+        if (catalogName != null) {
+            // Can be null for placeholder XML
+            oo.writeUTF(catalogName);
+        }
+        oo.writeObject(versions);
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultVersionedCatalog that = (DefaultVersionedCatalog) o;
+
+        if (versions != null ? !versions.equals(that.versions) : that.versions != null) {
+            return false;
+        }
+        return catalogName != null ? catalogName.equals(that.catalogName) : that.catalogName == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = versions != null ? versions.hashCode() : 0;
+        result = 31 * result + (catalogName != null ? catalogName.hashCode() : 0);
+        return result;
+    }
+
+    public void initialize(final Clock clock, final DefaultVersionedCatalog tenantCatalog) {
+        this.clock = clock;
+        initialize(tenantCatalog);
     }
 
     private static class CatalogPlanEntry {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java b/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
index f97f26d..b7ec777 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
@@ -26,8 +26,8 @@ import org.killbill.billing.catalog.api.DefaultCatalogInternalApi;
 import org.killbill.billing.catalog.api.user.DefaultCatalogUserApi;
 import org.killbill.billing.catalog.caching.CatalogCache;
 import org.killbill.billing.catalog.caching.CatalogCacheInvalidationCallback;
-import org.killbill.billing.catalog.caching.EhCacheCatalogCache;
-import org.killbill.billing.catalog.caching.EhCacheOverriddenPlanCache;
+import org.killbill.billing.catalog.caching.DefaultCatalogCache;
+import org.killbill.billing.catalog.caching.DefaultOverriddenPlanCache;
 import org.killbill.billing.catalog.caching.OverriddenPlanCache;
 import org.killbill.billing.catalog.dao.CatalogOverrideDao;
 import org.killbill.billing.catalog.dao.DefaultCatalogOverrideDao;
@@ -79,10 +79,10 @@ public class CatalogModule extends KillBillModule {
     }
 
     public void installCatalogConfigCache() {
-        bind(CatalogCache.class).to(EhCacheCatalogCache.class).asEagerSingleton();
+        bind(CatalogCache.class).to(DefaultCatalogCache.class).asEagerSingleton();
         bind(CacheInvalidationCallback.class).annotatedWith(Names.named(CATALOG_INVALIDATION_CALLBACK)).to(CatalogCacheInvalidationCallback.class).asEagerSingleton();
 
-        bind(OverriddenPlanCache.class).to(EhCacheOverriddenPlanCache.class).asEagerSingleton();
+        bind(OverriddenPlanCache.class).to(DefaultOverriddenPlanCache.class).asEagerSingleton();
     }
 
     protected void installCatalogPluginApi() {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java b/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
index 86085c2..103c45b 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
@@ -87,7 +87,7 @@ public class VersionedCatalogLoader implements CatalogLoader {
                 result.add(new StandaloneCatalogWithPriceOverride(catalog, priceOverride, InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID, internalCallContextFactory));
             }
             // Perform initialization and validation for VersionedCatalog
-            XMLLoader.initializeAndValidate(new URI(uriString), result);
+            XMLLoader.initializeAndValidate(result);
             return result;
         } catch (final ValidationException e) {
             logger.warn("Failed to load default catalog", e);
@@ -121,13 +121,13 @@ public class VersionedCatalogLoader implements CatalogLoader {
             uri = new URI("/tenantCatalog");
             for (final String cur : catalogXMLs) {
                 final InputStream curCatalogStream = new ByteArrayInputStream(cur.getBytes());
-                final StandaloneCatalog catalog = XMLLoader.getObjectFromStream(uri, curCatalogStream, StandaloneCatalog.class);
+                final StandaloneCatalog catalog = XMLLoader.getObjectFromStream(curCatalogStream, StandaloneCatalog.class);
                 if (!filterTemplateCatalog || !catalog.isTemplateCatalog()) {
                     result.add(new StandaloneCatalogWithPriceOverride(catalog, priceOverride, tenantRecordId, internalCallContextFactory));
                 }
             }
             // Perform initialization and validation for VersionedCatalog
-            XMLLoader.initializeAndValidate(uri, result);
+            XMLLoader.initializeAndValidate(result);
             return result;
         } catch (final ValidationException e) {
             logger.warn("Failed to load catalog for tenantRecordId='{}'", tenantRecordId, e);
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java b/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java
index 56ae3fb..518bee9 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java
@@ -17,8 +17,8 @@
 
 package org.killbill.billing.catalog.override;
 
-import java.util.List;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.regex.Pattern;
 
@@ -40,7 +40,6 @@ import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-import org.killbill.billing.catalog.api.StaticCatalog;
 import org.killbill.billing.catalog.api.Tier;
 import org.killbill.billing.catalog.api.TierPriceOverride;
 import org.killbill.billing.catalog.api.TieredBlock;
@@ -72,7 +71,6 @@ public class DefaultPriceOverride implements PriceOverride {
 
     @Override
     public DefaultPlan getOrCreateOverriddenPlan(final StandaloneCatalog standaloneCatalog, final Plan parentPlan, final DateTime catalogEffectiveDate, final List<PlanPhasePriceOverride> overrides, @Nullable final InternalCallContext context) throws CatalogApiException {
-
         final PlanPhasePriceOverride[] resolvedOverride = new PlanPhasePriceOverride[parentPlan.getAllPhases().length];
         int index = 0;
         for (final PlanPhase curPhase : parentPlan.getAllPhases()) {
@@ -92,12 +90,11 @@ public class DefaultPriceOverride implements PriceOverride {
                 }
             }).orNull();
 
-            if(curOverride != null) {
+            if (curOverride != null) {
                 List<UsagePriceOverride> resolvedUsageOverrides = getResolvedUsageOverrides(curPhase.getUsages(), curOverride.getUsagePriceOverrides());
                 resolvedOverride[index++] = new DefaultPlanPhasePriceOverride(curPhase.getName(), curOverride.getCurrency(), curOverride.getFixedPrice(),
-                        curOverride.getRecurringPrice(), resolvedUsageOverrides);
-            }
-            else {
+                                                                              curOverride.getRecurringPrice(), resolvedUsageOverrides);
+            } else {
                 resolvedOverride[index++] = null;
             }
         }
@@ -128,14 +125,14 @@ public class DefaultPriceOverride implements PriceOverride {
         }
 
         final DefaultPlan result = new DefaultPlan(standaloneCatalog, planName, (DefaultPlan) parentPlan, resolvedOverride);
-        result.initialize(standaloneCatalog, standaloneCatalog.getCatalogURI());
+        result.initialize(standaloneCatalog);
         if (context == null) {
             overriddenPlanCache.addDryRunPlan(planName, result);
         }
         return result;
     }
 
-    public List<UsagePriceOverride> getResolvedUsageOverrides(Usage[] usages, List<UsagePriceOverride> usagePriceOverrides) throws CatalogApiException{
+    public List<UsagePriceOverride> getResolvedUsageOverrides(Usage[] usages, List<UsagePriceOverride> usagePriceOverrides) throws CatalogApiException {
         List<UsagePriceOverride> resolvedUsageOverrides = new ArrayList<UsagePriceOverride>();
 
         for (final Usage curUsage : usages) {
@@ -145,18 +142,18 @@ public class DefaultPriceOverride implements PriceOverride {
                     return input.getName() != null && input.getName().equals(curUsage.getName());
                 }
             }).orNull();
-              if(curOverride != null) {
-                  List<TierPriceOverride>  tierPriceOverrides = getResolvedTierOverrides(curUsage.getTiers(), curOverride.getTierPriceOverrides());
-                  resolvedUsageOverrides.add(new DefaultUsagePriceOverride(curUsage.getName(), curUsage.getUsageType(),tierPriceOverrides));
-              } else {
-                  resolvedUsageOverrides.add(null);
-              }
+            if (curOverride != null) {
+                List<TierPriceOverride> tierPriceOverrides = getResolvedTierOverrides(curUsage.getTiers(), curOverride.getTierPriceOverrides());
+                resolvedUsageOverrides.add(new DefaultUsagePriceOverride(curUsage.getName(), curUsage.getUsageType(), tierPriceOverrides));
+            } else {
+                resolvedUsageOverrides.add(null);
+            }
         }
 
         return resolvedUsageOverrides;
     }
 
-    public List<TierPriceOverride> getResolvedTierOverrides(Tier[] tiers, List<TierPriceOverride> tierPriceOverrides) throws CatalogApiException{
+    public List<TierPriceOverride> getResolvedTierOverrides(Tier[] tiers, List<TierPriceOverride> tierPriceOverrides) throws CatalogApiException {
         List<TierPriceOverride> resolvedTierOverrides = new ArrayList<TierPriceOverride>();
 
         for (final Tier curTier : tiers) {
@@ -173,8 +170,8 @@ public class DefaultPriceOverride implements PriceOverride {
                             for (int i = 0; i < curTier.getTieredBlocks().length; i++) {
                                 TieredBlock curTieredBlock = curTier.getTieredBlocks()[i];
                                 if (unitName.equals(curTieredBlock.getUnit().getName()) &&
-                                        Double.compare(size, curTieredBlock.getSize()) == 0 &&
-                                        Double.compare(max, curTieredBlock.getMax()) == 0) {
+                                    Double.compare(size, curTieredBlock.getSize()) == 0 &&
+                                    Double.compare(max, curTieredBlock.getMax()) == 0) {
                                     return true;
                                 }
                             }
@@ -184,12 +181,11 @@ public class DefaultPriceOverride implements PriceOverride {
                 }
             }).orNull();
 
-            if(curOverride != null) {
+            if (curOverride != null) {
                 List<TieredBlockPriceOverride> tieredBlockPriceOverrides = getResolvedTieredBlockPriceOverrides(curTier.getTieredBlocks(),
-                        curOverride.getTieredBlockPriceOverrides());
+                                                                                                                curOverride.getTieredBlockPriceOverrides());
                 resolvedTierOverrides.add(new DefaultTierPriceOverride(tieredBlockPriceOverrides));
-            }
-            else {
+            } else {
                 resolvedTierOverrides.add(null);
             }
         }
@@ -206,25 +202,23 @@ public class DefaultPriceOverride implements PriceOverride {
                 @Override
                 public boolean apply(final TieredBlockPriceOverride input) {
                     return input.getUnitName() != null && input.getSize() != null && input.getMax() != null &&
-                            (input.getUnitName().equals(curTieredBlock.getUnit().getName()) &&
-                             Double.compare(input.getSize(), curTieredBlock.getSize()) == 0 &&
-                             Double.compare(input.getMax(), curTieredBlock.getMax()) == 0);
+                           (input.getUnitName().equals(curTieredBlock.getUnit().getName()) &&
+                            Double.compare(input.getSize(), curTieredBlock.getSize()) == 0 &&
+                            Double.compare(input.getMax(), curTieredBlock.getMax()) == 0);
                 }
             }).orNull();
 
-            if(curOverride != null) {
-                resolvedTieredBlockPriceOverrides.add(new DefaultTieredBlockPriceOverride(curTieredBlock.getUnit().getName(), curOverride.getSize(), curOverride.getPrice(), curOverride.getCurrency(), curOverride.getMax())) ;
-            }
-            else {
+            if (curOverride != null) {
+                resolvedTieredBlockPriceOverrides.add(new DefaultTieredBlockPriceOverride(curTieredBlock.getUnit().getName(), curOverride.getSize(), curOverride.getPrice(), curOverride.getCurrency(), curOverride.getMax()));
+            } else {
                 resolvedTieredBlockPriceOverrides.add(null);
             }
         }
         return resolvedTieredBlockPriceOverrides;
     }
 
-
     @Override
-    public DefaultPlan getOverriddenPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException {
+    public DefaultPlan getOverriddenPlan(final String planName, final StandaloneCatalog catalog, final InternalTenantContext context) throws CatalogApiException {
         return overriddenPlanCache.getOverriddenPlan(planName, catalog, context);
     }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/override/PriceOverride.java b/catalog/src/main/java/org/killbill/billing/catalog/override/PriceOverride.java
index cb81b5b..da47bdc 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/override/PriceOverride.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/override/PriceOverride.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -24,16 +24,13 @@ import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.DefaultPlan;
 import org.killbill.billing.catalog.StandaloneCatalog;
-import org.killbill.billing.catalog.StandaloneCatalogWithPriceOverride;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
-import org.killbill.billing.catalog.api.StaticCatalog;
 
 public interface PriceOverride {
 
     DefaultPlan getOrCreateOverriddenPlan(final StandaloneCatalog catalog, final Plan parentPlan, final DateTime catalogEffectiveDate, final List<PlanPhasePriceOverride> overrides, final InternalCallContext context) throws CatalogApiException;
 
-
-    DefaultPlan getOverriddenPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException;
+    DefaultPlan getOverriddenPlan(final String planName, final StandaloneCatalog catalog, final InternalTenantContext context) throws CatalogApiException;
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java b/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
index e6cedcd..c03ea8e 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
@@ -103,7 +103,7 @@ public class StandaloneCatalogMapper {
         this.tmpDefaultPriceListMap = new HashMap<String, DefaultPriceList>();
     }
 
-    public StandaloneCatalog toStandaloneCatalog(final StandalonePluginCatalog pluginCatalog, @Nullable URI catalogURI) {
+    public StandaloneCatalog toStandaloneCatalog(final StandalonePluginCatalog pluginCatalog) {
 
         final StandaloneCatalog result = new StandaloneCatalog();
         result.setCatalogName(catalogName);
@@ -121,7 +121,7 @@ public class StandaloneCatalogMapper {
                 ((DefaultProduct) target).setIncluded(toFilteredDefaultProduct(cur.getIncluded()));
             }
         }
-        result.initialize(result, catalogURI);
+        result.initialize(result);
         return result;
     }
 
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/plugin/VersionedCatalogMapper.java b/catalog/src/main/java/org/killbill/billing/catalog/plugin/VersionedCatalogMapper.java
index bdbab8a..dd365d4 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/plugin/VersionedCatalogMapper.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/plugin/VersionedCatalogMapper.java
@@ -54,7 +54,7 @@ public class VersionedCatalogMapper {
 
     private StandaloneCatalogWithPriceOverride toStandaloneCatalogWithPriceOverride(final VersionedPluginCatalog pluginCatalog, final StandalonePluginCatalog input, final InternalTenantContext internalTenantContext) {
         final StandaloneCatalogMapper mapper = new StandaloneCatalogMapper(pluginCatalog.getCatalogName());
-        final StandaloneCatalog catalog = mapper.toStandaloneCatalog(input, null);
+        final StandaloneCatalog catalog = mapper.toStandaloneCatalog(input);
         final StandaloneCatalogWithPriceOverride result = new StandaloneCatalogWithPriceOverride(catalog, priceOverride, internalTenantContext.getTenantRecordId(), internalCallContextFactory);
         return result;
     }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/PriceListDefault.java b/catalog/src/main/java/org/killbill/billing/catalog/PriceListDefault.java
index 08571ef..3005ca4 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/PriceListDefault.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/PriceListDefault.java
@@ -16,8 +16,6 @@
 
 package org.killbill.billing.catalog;
 
-import java.net.URI;
-
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 
@@ -40,15 +38,15 @@ public class PriceListDefault extends DefaultPriceList {
         super.validate(catalog, errors);
         if (!getName().equals(PriceListSet.DEFAULT_PRICELIST_NAME)) {
             errors.add(new ValidationError("The name of the default pricelist must be 'DEFAULT'",
-                                           catalog.getCatalogURI(), DefaultPriceList.class, getName()));
+                                           DefaultPriceList.class, getName()));
 
         }
         return errors;
     }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
     }
 
@@ -57,5 +55,4 @@ public class PriceListDefault extends DefaultPriceList {
         return PriceListSet.DEFAULT_PRICELIST_NAME;
     }
 
-
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCase.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCase.java
index 99e23d4..933a578 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCase.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCase.java
@@ -95,8 +95,8 @@ public abstract class DefaultCase<T> extends ValidatingConfig<StandaloneCatalog>
     }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
     }
 
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseBillingAlignment.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseBillingAlignment.java
index 31e56fc..9efc4b9 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseBillingAlignment.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseBillingAlignment.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,13 +18,18 @@
 
 package org.killbill.billing.catalog.rules;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
 import javax.xml.bind.annotation.XmlElement;
 
 import org.killbill.billing.catalog.api.BillingAlignment;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.rules.CaseBillingAlignment;
 
-public class DefaultCaseBillingAlignment extends DefaultCasePhase<BillingAlignment> implements CaseBillingAlignment {
+public class DefaultCaseBillingAlignment extends DefaultCasePhase<BillingAlignment> implements CaseBillingAlignment, Externalizable {
 
     @XmlElement(required = true)
     private BillingAlignment alignment;
@@ -87,4 +94,18 @@ public class DefaultCaseBillingAlignment extends DefaultCasePhase<BillingAlignme
                '}';
     }
 
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        super.writeExternal(out);
+        out.writeBoolean(alignment != null);
+        if (alignment != null) {
+            out.writeUTF(alignment.name());
+        }
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        super.readExternal(in);
+        this.alignment = in.readBoolean() ? BillingAlignment.valueOf(in.readUTF()) : null;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseCancelPolicy.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseCancelPolicy.java
index 2d21e94..23c000f 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseCancelPolicy.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseCancelPolicy.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,6 +18,11 @@
 
 package org.killbill.billing.catalog.rules;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
 import javax.xml.bind.annotation.XmlElement;
 
 import org.killbill.billing.catalog.StandaloneCatalog;
@@ -25,7 +32,7 @@ import org.killbill.billing.catalog.api.rules.CaseCancelPolicy;
 import org.killbill.xmlloader.ValidationError;
 import org.killbill.xmlloader.ValidationErrors;
 
-public class DefaultCaseCancelPolicy extends DefaultCasePhase<BillingActionPolicy> implements CaseCancelPolicy {
+public class DefaultCaseCancelPolicy extends DefaultCasePhase<BillingActionPolicy> implements CaseCancelPolicy, Externalizable {
 
     @XmlElement(required = true)
     private BillingActionPolicy policy;
@@ -42,14 +49,13 @@ public class DefaultCaseCancelPolicy extends DefaultCasePhase<BillingActionPolic
 
     @Override
     public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {
-        if (policy ==  BillingActionPolicy.START_OF_TERM) {
+        if (policy == BillingActionPolicy.START_OF_TERM) {
             errors.add(new ValidationError("Default catalog START_OF_TERM has not been implemented, such policy can be used during cancellation by overriding policy",
-                                           catalog.getCatalogURI(), DefaultCaseCancelPolicy.class, ""));
+                                           DefaultCaseCancelPolicy.class, ""));
         }
         return errors;
     }
 
-
     @Override
     public BillingActionPolicy getBillingActionPolicy() {
         return policy;
@@ -100,4 +106,18 @@ public class DefaultCaseCancelPolicy extends DefaultCasePhase<BillingActionPolic
                '}';
     }
 
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        super.writeExternal(out);
+        out.writeBoolean(policy != null);
+        if (policy != null) {
+            out.writeUTF(policy.name());
+        }
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        super.readExternal(in);
+        this.policy = in.readBoolean() ? BillingActionPolicy.valueOf(in.readUTF()) : null;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseChange.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseChange.java
index d522bff..99f80a5 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseChange.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseChange.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,6 +18,10 @@
 
 package org.killbill.billing.catalog.rules;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.net.URI;
 
 import javax.xml.bind.annotation.XmlAccessType;
@@ -42,7 +48,7 @@ import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public abstract class DefaultCaseChange<T> extends ValidatingConfig<StandaloneCatalog> implements CaseChange {
+public abstract class DefaultCaseChange<T> extends ValidatingConfig<StandaloneCatalog> implements CaseChange, Externalizable {
 
     @XmlElement(required = false)
     protected PhaseType phaseType;
@@ -80,8 +86,6 @@ public abstract class DefaultCaseChange<T> extends ValidatingConfig<StandaloneCa
     public T getResult(final PlanPhaseSpecifier from,
                        final PlanSpecifier to, final StaticCatalog catalog) throws CatalogApiException {
 
-
-
         final Product inFromProduct;
         final BillingPeriod inFromBillingPeriod;
         final ProductCategory inFromProductCategory;
@@ -116,7 +120,6 @@ public abstract class DefaultCaseChange<T> extends ValidatingConfig<StandaloneCa
             inToPriceList = to.getPriceListName() != null ? catalog.findCurrentPricelist(to.getPriceListName()) : null;
         }
 
-
         if (
                 (phaseType == null || from.getPhaseType() == phaseType) &&
                 (fromProduct == null || fromProduct.equals(inFromProduct)) &&
@@ -127,7 +130,7 @@ public abstract class DefaultCaseChange<T> extends ValidatingConfig<StandaloneCa
                 (this.toBillingPeriod == null || this.toBillingPeriod.equals(inToBillingPeriod)) &&
                 (fromPriceList == null || fromPriceList.equals(inFromPriceList)) &&
                 (toPriceList == null || toPriceList.equals(inToPriceList))
-                ) {
+        ) {
             return getResult();
         }
         return null;
@@ -153,8 +156,8 @@ public abstract class DefaultCaseChange<T> extends ValidatingConfig<StandaloneCa
     }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
     }
 
@@ -318,4 +321,45 @@ public abstract class DefaultCaseChange<T> extends ValidatingConfig<StandaloneCa
                ", toPriceList=" + toPriceList +
                '}';
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeBoolean(phaseType != null);
+        if (phaseType != null) {
+            out.writeUTF(phaseType.name());
+        }
+        out.writeObject(fromProduct);
+        out.writeBoolean(fromProductCategory != null);
+        if (fromProductCategory != null) {
+            out.writeUTF(fromProductCategory.name());
+        }
+        out.writeBoolean(fromBillingPeriod != null);
+        if (fromBillingPeriod != null) {
+            out.writeUTF(fromBillingPeriod.name());
+        }
+        out.writeObject(fromPriceList);
+        out.writeObject(toProduct);
+        out.writeBoolean(toProductCategory != null);
+        if (toProductCategory != null) {
+            out.writeUTF(toProductCategory.name());
+        }
+        out.writeBoolean(toBillingPeriod != null);
+        if (toBillingPeriod != null) {
+            out.writeUTF(toBillingPeriod.name());
+        }
+        out.writeObject(toPriceList);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.phaseType = in.readBoolean() ? PhaseType.valueOf(in.readUTF()) : null;
+        this.fromProduct = (DefaultProduct) in.readObject();
+        this.fromProductCategory = in.readBoolean() ? ProductCategory.valueOf(in.readUTF()) : null;
+        this.fromBillingPeriod = in.readBoolean() ? BillingPeriod.valueOf(in.readUTF()) : null;
+        this.fromPriceList = (DefaultPriceList) in.readObject();
+        this.toProduct = (DefaultProduct) in.readObject();
+        this.toProductCategory = in.readBoolean() ? ProductCategory.valueOf(in.readUTF()) : null;
+        this.toBillingPeriod = in.readBoolean() ? BillingPeriod.valueOf(in.readUTF()) : null;
+        this.toPriceList = (DefaultPriceList) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseChangePlanAlignment.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseChangePlanAlignment.java
index d3a95b6..b482e86 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseChangePlanAlignment.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseChangePlanAlignment.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,12 +18,17 @@
 
 package org.killbill.billing.catalog.rules;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
 import javax.xml.bind.annotation.XmlElement;
 
 import org.killbill.billing.catalog.api.PlanAlignmentChange;
 import org.killbill.billing.catalog.api.rules.CaseChangePlanAlignment;
 
-public class DefaultCaseChangePlanAlignment extends DefaultCaseChange<PlanAlignmentChange> implements CaseChangePlanAlignment {
+public class DefaultCaseChangePlanAlignment extends DefaultCaseChange<PlanAlignmentChange> implements CaseChangePlanAlignment, Externalizable {
 
     @XmlElement(required = true)
     private PlanAlignmentChange alignment;
@@ -85,4 +92,18 @@ public class DefaultCaseChangePlanAlignment extends DefaultCaseChange<PlanAlignm
                '}';
     }
 
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        super.writeExternal(out);
+        out.writeBoolean(alignment != null);
+        if (alignment != null) {
+            out.writeUTF(alignment.name());
+        }
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        super.readExternal(in);
+        this.alignment = in.readBoolean() ? PlanAlignmentChange.valueOf(in.readUTF()) : null;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseChangePlanPolicy.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseChangePlanPolicy.java
index 71f90b5..f1cbf27 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseChangePlanPolicy.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseChangePlanPolicy.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,6 +18,10 @@
 
 package org.killbill.billing.catalog.rules;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.net.URI;
 
 import javax.xml.bind.annotation.XmlElement;
@@ -28,11 +34,15 @@ import org.killbill.billing.catalog.api.rules.CaseChangePlanPolicy;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlSeeAlso(DefaultCaseChange.class)
-public class DefaultCaseChangePlanPolicy extends DefaultCaseChange<BillingActionPolicy> implements CaseChangePlanPolicy {
+public class DefaultCaseChangePlanPolicy extends DefaultCaseChange<BillingActionPolicy> implements CaseChangePlanPolicy, Externalizable {
 
     @XmlElement(required = true)
     private BillingActionPolicy policy;
 
+    // Required for deserialization
+    public DefaultCaseChangePlanPolicy() {
+    }
+
     @Override
     protected BillingActionPolicy getResult() {
         return policy;
@@ -54,8 +64,8 @@ public class DefaultCaseChangePlanPolicy extends DefaultCaseChange<BillingAction
     }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
     }
 
@@ -91,7 +101,7 @@ public class DefaultCaseChangePlanPolicy extends DefaultCaseChange<BillingAction
     public String toString() {
         return "DefaultCaseChangePlanPolicy {" +
                "policy=" + policy +
-               ", phaseType=" + phaseType +
+               ", phaseType=" + getPhaseType() +
                ", fromProduct=" + getFromProduct() +
                ", fromProductCategory=" + getFromProductCategory() +
                ", fromBillingPeriod=" + getFromBillingPeriod() +
@@ -102,4 +112,19 @@ public class DefaultCaseChangePlanPolicy extends DefaultCaseChange<BillingAction
                ", toPriceList=" + getToPriceList() +
                '}';
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        super.writeExternal(out);
+        out.writeBoolean(policy != null);
+        if (policy != null) {
+            out.writeUTF(policy.name());
+        }
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        super.readExternal(in);
+        this.policy = in.readBoolean() ? BillingActionPolicy.valueOf(in.readUTF()) : null;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseCreateAlignment.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseCreateAlignment.java
index 5577db3..27a5db5 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseCreateAlignment.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseCreateAlignment.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,12 +18,17 @@
 
 package org.killbill.billing.catalog.rules;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
 import javax.xml.bind.annotation.XmlElement;
 
 import org.killbill.billing.catalog.api.PlanAlignmentCreate;
 import org.killbill.billing.catalog.api.rules.CaseCreateAlignment;
 
-public class DefaultCaseCreateAlignment extends DefaultCaseStandardNaming<PlanAlignmentCreate> implements CaseCreateAlignment {
+public class DefaultCaseCreateAlignment extends DefaultCaseStandardNaming<PlanAlignmentCreate> implements CaseCreateAlignment, Externalizable {
 
     @XmlElement(required = true)
     private PlanAlignmentCreate alignment;
@@ -80,4 +87,18 @@ public class DefaultCaseCreateAlignment extends DefaultCaseStandardNaming<PlanAl
                '}';
     }
 
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        super.writeExternal(out);
+        out.writeBoolean(alignment != null);
+        if (alignment != null) {
+            out.writeUTF(alignment.name());
+        }
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        super.readExternal(in);
+        this.alignment = in.readBoolean() ? PlanAlignmentCreate.valueOf(in.readUTF()) : null;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCasePhase.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCasePhase.java
index ef8773c..1e01885 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCasePhase.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCasePhase.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,6 +18,10 @@
 
 package org.killbill.billing.catalog.rules;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.net.URI;
 
 import javax.xml.bind.annotation.XmlElement;
@@ -29,14 +35,14 @@ import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.StaticCatalog;
 import org.killbill.xmlloader.ValidationErrors;
 
-public abstract class DefaultCasePhase<T> extends DefaultCaseStandardNaming<T> {
+public abstract class DefaultCasePhase<T> extends DefaultCaseStandardNaming<T> implements Externalizable {
 
     @XmlElement(required = false)
     protected PhaseType phaseType;
 
     public T getResult(final PlanPhaseSpecifier specifier, final StaticCatalog c) throws CatalogApiException {
         if ((phaseType == null || specifier.getPhaseType() == phaseType)
-                && satisfiesCase(new PlanSpecifier(specifier), c)) {
+            && satisfiesCase(new PlanSpecifier(specifier), c)) {
             return getResult();
         }
         return null;
@@ -61,12 +67,11 @@ public abstract class DefaultCasePhase<T> extends DefaultCaseStandardNaming<T> {
     }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
     }
 
-
     public DefaultCasePhase<T> setPhaseType(final PhaseType phaseType) {
         this.phaseType = phaseType;
         return this;
@@ -99,4 +104,19 @@ public abstract class DefaultCasePhase<T> extends DefaultCaseStandardNaming<T> {
         result = 31 * result + (phaseType != null ? phaseType.hashCode() : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        super.writeExternal(out);
+        out.writeBoolean(phaseType != null);
+        if (phaseType != null) {
+            out.writeUTF(phaseType.name());
+        }
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        super.readExternal(in);
+        this.phaseType = in.readBoolean() ? PhaseType.valueOf(in.readUTF()) : null;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCasePriceList.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCasePriceList.java
index 4b9cf33..ad23086 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCasePriceList.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCasePriceList.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,6 +18,11 @@
 
 package org.killbill.billing.catalog.rules;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlIDREF;
 
@@ -26,7 +33,8 @@ import org.killbill.billing.catalog.api.PriceList;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.catalog.api.rules.CasePriceList;
 
-public class DefaultCasePriceList extends DefaultCaseStandardNaming<DefaultPriceList> implements CasePriceList {
+public class DefaultCasePriceList extends DefaultCaseStandardNaming<DefaultPriceList> implements CasePriceList, Externalizable {
+
     @XmlElement(required = false, name = "fromProduct")
     @XmlIDREF
     private DefaultProduct fromProduct;
@@ -94,7 +102,6 @@ public class DefaultCasePriceList extends DefaultCaseStandardNaming<DefaultPrice
         return this;
     }
 
-
     public DefaultCasePriceList setToPriceList(final DefaultPriceList toPriceList) {
         this.toPriceList = toPriceList;
         return this;
@@ -144,7 +151,6 @@ public class DefaultCasePriceList extends DefaultCaseStandardNaming<DefaultPrice
         return result;
     }
 
-
     @Override
     public String toString() {
         return "DefaultCasePriceList {" +
@@ -155,4 +161,30 @@ public class DefaultCasePriceList extends DefaultCaseStandardNaming<DefaultPrice
                ", toPriceList=" + toPriceList +
                '}';
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        super.writeExternal(out);
+        out.writeObject(fromProduct);
+        out.writeBoolean(fromProductCategory != null);
+        if (fromProductCategory != null) {
+            out.writeUTF(fromProductCategory.name());
+        }
+        out.writeBoolean(fromBillingPeriod != null);
+        if (fromBillingPeriod != null) {
+            out.writeUTF(fromBillingPeriod.name());
+        }
+        out.writeObject(fromPriceList);
+        out.writeObject(toPriceList);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        super.readExternal(in);
+        this.fromProduct = (DefaultProduct) in.readObject();
+        this.fromProductCategory = in.readBoolean() ? ProductCategory.valueOf(in.readUTF()) : null;
+        this.fromBillingPeriod = in.readBoolean() ? BillingPeriod.valueOf(in.readUTF()) : null;
+        this.fromPriceList = (DefaultPriceList) in.readObject();
+        this.toPriceList = (DefaultPriceList) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseStandardNaming.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseStandardNaming.java
index d23c55f..3c97746 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseStandardNaming.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultCaseStandardNaming.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,6 +18,11 @@
 
 package org.killbill.billing.catalog.rules;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlIDREF;
 
@@ -26,7 +33,8 @@ import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.catalog.api.rules.Case;
 
-public abstract class DefaultCaseStandardNaming<T> extends DefaultCase<T> implements Case {
+public abstract class DefaultCaseStandardNaming<T> extends DefaultCase<T> implements Case, Externalizable {
+
     @XmlElement(required = false, name = "product")
     @XmlIDREF
     private DefaultProduct product;
@@ -116,4 +124,25 @@ public abstract class DefaultCaseStandardNaming<T> extends DefaultCase<T> implem
         return result;
     }
 
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeObject(product);
+        out.writeBoolean(productCategory != null);
+        if (productCategory != null) {
+            out.writeUTF(productCategory.name());
+        }
+        out.writeBoolean(billingPeriod != null);
+        if (billingPeriod != null) {
+            out.writeUTF(billingPeriod.name());
+        }
+        out.writeObject(priceList);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.product = (DefaultProduct) in.readObject();
+        this.productCategory = in.readBoolean() ? ProductCategory.valueOf(in.readUTF()) : null;
+        this.billingPeriod = in.readBoolean() ? BillingPeriod.valueOf(in.readUTF()) : null;
+        this.priceList = (DefaultPriceList) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultPlanRules.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultPlanRules.java
index 6085476..0a18078 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultPlanRules.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/DefaultPlanRules.java
@@ -18,7 +18,10 @@
 
 package org.killbill.billing.catalog.rules;
 
-import java.net.URI;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.util.Arrays;
 import java.util.HashSet;
 
@@ -54,7 +57,7 @@ import org.killbill.xmlloader.ValidationErrors;
 import com.google.common.collect.ImmutableList;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultPlanRules extends ValidatingConfig<StandaloneCatalog> implements PlanRules {
+public class DefaultPlanRules extends ValidatingConfig<StandaloneCatalog> implements PlanRules, Externalizable {
 
     @XmlElementWrapper(name = "changePolicy")
     @XmlElement(name = "changePolicyCase", required = false)
@@ -80,6 +83,10 @@ public class DefaultPlanRules extends ValidatingConfig<StandaloneCatalog> implem
     @XmlElement(name = "priceListCase", required = false)
     private DefaultCasePriceList[] priceListCase;
 
+    // Required for deserialization
+    public DefaultPlanRules() {
+    }
+
     @Override
     public Iterable<CaseChangePlanPolicy> getCaseChangePlanPolicy() {
         return ImmutableList.<CaseChangePlanPolicy>copyOf(changeCase);
@@ -137,7 +144,6 @@ public class DefaultPlanRules extends ValidatingConfig<StandaloneCatalog> implem
                                               new PlanSpecifier(to.getProductName(), to.getBillingPeriod(), toPriceList.getName()) :
                                               to;
 
-
         final BillingActionPolicy policy = getPlanChangePolicy(from, toWithPriceList, catalog);
         if (policy == BillingActionPolicy.ILLEGAL) {
             throw new IllegalPlanChange(from, toWithPriceList);
@@ -178,7 +184,7 @@ public class DefaultPlanRules extends ValidatingConfig<StandaloneCatalog> implem
         boolean foundDefaultCase = false;
         for (final DefaultCaseChangePlanPolicy cur : changeCase) {
             if (caseChangePlanPoliciesSet.contains(cur)) {
-                errors.add(new ValidationError(String.format("Duplicate rule for change plan %s", cur.toString()), catalog.getCatalogURI(), DefaultPlanRules.class, ""));
+                errors.add(new ValidationError(String.format("Duplicate rule for change plan %s", cur.toString()), DefaultPlanRules.class, ""));
             } else {
                 caseChangePlanPoliciesSet.add(cur);
             }
@@ -196,14 +202,14 @@ public class DefaultPlanRules extends ValidatingConfig<StandaloneCatalog> implem
             cur.validate(catalog, errors);
         }
         if (!foundDefaultCase) {
-            errors.add(new ValidationError("Missing default rule case for plan change", catalog.getCatalogURI(), DefaultPlanRules.class, ""));
+            errors.add(new ValidationError("Missing default rule case for plan change", DefaultPlanRules.class, ""));
         }
 
         final HashSet<DefaultCaseCancelPolicy> defaultCaseCancelPoliciesSet = new HashSet<DefaultCaseCancelPolicy>();
         foundDefaultCase = false;
         for (final DefaultCaseCancelPolicy cur : cancelCase) {
             if (defaultCaseCancelPoliciesSet.contains(cur)) {
-                errors.add(new ValidationError(String.format("Duplicate rule for plan cancellation %s", cur.toString()), catalog.getCatalogURI(), DefaultPlanRules.class, ""));
+                errors.add(new ValidationError(String.format("Duplicate rule for plan cancellation %s", cur.toString()), DefaultPlanRules.class, ""));
             } else {
                 defaultCaseCancelPoliciesSet.add(cur);
             }
@@ -217,14 +223,13 @@ public class DefaultPlanRules extends ValidatingConfig<StandaloneCatalog> implem
             cur.validate(catalog, errors);
         }
         if (!foundDefaultCase) {
-            errors.add(new ValidationError("Missing default rule case for plan cancellation", catalog.getCatalogURI(), DefaultPlanRules.class, ""));
+            errors.add(new ValidationError("Missing default rule case for plan cancellation", DefaultPlanRules.class, ""));
         }
 
-
         final HashSet<DefaultCaseChangePlanAlignment> caseChangePlanAlignmentsSet = new HashSet<DefaultCaseChangePlanAlignment>();
         for (final DefaultCaseChangePlanAlignment cur : changeAlignmentCase) {
             if (caseChangePlanAlignmentsSet.contains(cur)) {
-                errors.add(new ValidationError(String.format("Duplicate rule for plan change alignment %s", cur.toString()), catalog.getCatalogURI(), DefaultPlanRules.class, ""));
+                errors.add(new ValidationError(String.format("Duplicate rule for plan change alignment %s", cur.toString()), DefaultPlanRules.class, ""));
             } else {
                 caseChangePlanAlignmentsSet.add(cur);
             }
@@ -234,7 +239,7 @@ public class DefaultPlanRules extends ValidatingConfig<StandaloneCatalog> implem
         final HashSet<DefaultCaseCreateAlignment> caseCreateAlignmentsSet = new HashSet<DefaultCaseCreateAlignment>();
         for (final DefaultCaseCreateAlignment cur : createAlignmentCase) {
             if (caseCreateAlignmentsSet.contains(cur)) {
-                errors.add(new ValidationError(String.format("Duplicate rule for create plan alignment %s", cur.toString()), catalog.getCatalogURI(), DefaultPlanRules.class, ""));
+                errors.add(new ValidationError(String.format("Duplicate rule for create plan alignment %s", cur.toString()), DefaultPlanRules.class, ""));
             } else {
                 caseCreateAlignmentsSet.add(cur);
             }
@@ -244,7 +249,7 @@ public class DefaultPlanRules extends ValidatingConfig<StandaloneCatalog> implem
         final HashSet<DefaultCaseBillingAlignment> caseBillingAlignmentsSet = new HashSet<DefaultCaseBillingAlignment>();
         for (final DefaultCaseBillingAlignment cur : billingAlignmentCase) {
             if (caseBillingAlignmentsSet.contains(cur)) {
-                errors.add(new ValidationError(String.format("Duplicate rule for billing alignment %s", cur.toString()), catalog.getCatalogURI(), DefaultPlanRules.class, ""));
+                errors.add(new ValidationError(String.format("Duplicate rule for billing alignment %s", cur.toString()), DefaultPlanRules.class, ""));
             } else {
                 caseBillingAlignmentsSet.add(cur);
             }
@@ -254,7 +259,7 @@ public class DefaultPlanRules extends ValidatingConfig<StandaloneCatalog> implem
         final HashSet<DefaultCasePriceList> casePriceListsSet = new HashSet<DefaultCasePriceList>();
         for (final DefaultCasePriceList cur : priceListCase) {
             if (casePriceListsSet.contains(cur)) {
-                errors.add(new ValidationError(String.format("Duplicate rule for price list transition %s", cur.toString()), catalog.getCatalogURI(), DefaultPlanRules.class, ""));
+                errors.add(new ValidationError(String.format("Duplicate rule for price list transition %s", cur.toString()), DefaultPlanRules.class, ""));
             } else {
                 casePriceListsSet.add(cur);
             }
@@ -263,33 +268,31 @@ public class DefaultPlanRules extends ValidatingConfig<StandaloneCatalog> implem
         return errors;
     }
 
-
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
 
         for (final DefaultCaseChangePlanPolicy cur : changeCase) {
-            cur.initialize(catalog, sourceURI);
+            cur.initialize(catalog);
         }
         for (final DefaultCaseChangePlanAlignment cur : changeAlignmentCase) {
-            cur.initialize(catalog, sourceURI);
+            cur.initialize(catalog);
         }
         for (final DefaultCaseCancelPolicy cur : cancelCase) {
-            cur.initialize(catalog, sourceURI);
+            cur.initialize(catalog);
         }
         for (final DefaultCaseCreateAlignment cur : createAlignmentCase) {
-            cur.initialize(catalog, sourceURI);
+            cur.initialize(catalog);
         }
         for (final DefaultCaseBillingAlignment cur : billingAlignmentCase) {
-            cur.initialize(catalog, sourceURI);
+            cur.initialize(catalog);
         }
         for (final DefaultCasePriceList cur : priceListCase) {
-            cur.initialize(catalog, sourceURI);
+            cur.initialize(catalog);
         }
     }
 
-
     /////////////////////////////////////////////////////////////////////////////////////
     // Setters for testing
     /////////////////////////////////////////////////////////////////////////////////////
@@ -369,4 +372,24 @@ public class DefaultPlanRules extends ValidatingConfig<StandaloneCatalog> implem
         result = 31 * result + (priceListCase != null ? Arrays.hashCode(priceListCase) : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeObject(changeCase);
+        out.writeObject(changeAlignmentCase);
+        out.writeObject(cancelCase);
+        out.writeObject(createAlignmentCase);
+        out.writeObject(billingAlignmentCase);
+        out.writeObject(priceListCase);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.changeCase = (DefaultCaseChangePlanPolicy[]) in.readObject();
+        this.changeAlignmentCase = (DefaultCaseChangePlanAlignment[]) in.readObject();
+        this.cancelCase = (DefaultCaseCancelPolicy[]) in.readObject();
+        this.createAlignmentCase = (DefaultCaseCreateAlignment[]) in.readObject();
+        this.billingAlignmentCase = (DefaultCaseBillingAlignment[]) in.readObject();
+        this.priceListCase = (DefaultCasePriceList[]) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
index 9d454b0..ed078fb 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
@@ -18,7 +18,10 @@
 
 package org.killbill.billing.catalog;
 
-import java.net.URI;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -58,7 +61,7 @@ import org.killbill.xmlloader.ValidationErrors;
 
 @XmlRootElement(name = "catalog")
 @XmlAccessorType(XmlAccessType.NONE)
-public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> implements StaticCatalog {
+public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> implements StaticCatalog, Externalizable {
 
     @XmlElement(required = true)
     private Date effectiveDate;
@@ -91,8 +94,6 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
     @XmlElement(name = "priceLists", required = true)
     private DefaultPriceListSet priceLists;
 
-    private URI catalogURI;
-
     public StandaloneCatalog() {
         this.plans = new CatalogEntityCollection<Plan>();
         this.products = new CatalogEntityCollection<Product>();
@@ -156,10 +157,6 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
                (supportedCurrencies == null || supportedCurrencies.length == 0);
     }
 
-    public URI getCatalogURI() {
-        return catalogURI;
-    }
-
     public DefaultPlanRules getPlanRules() {
         return planRules;
     }
@@ -294,22 +291,20 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
     }
 
     @Override
-    public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
-
-        super.initialize(catalog, sourceURI);
+    public void initialize(final StandaloneCatalog catalog) {
+        super.initialize(catalog);
         CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
 
-        catalogURI = sourceURI;
-        planRules.initialize(catalog, sourceURI);
-        priceLists.initialize(catalog, sourceURI);
+        planRules.initialize(catalog);
+        priceLists.initialize(catalog);
         for (final DefaultUnit cur : units) {
-            cur.initialize(catalog, sourceURI);
+            cur.initialize(catalog);
         }
         for (final Product p : products.getEntries()) {
-            ((DefaultProduct) p).initialize(catalog, sourceURI);
+            ((DefaultProduct) p).initialize(catalog);
         }
         for (final Plan p : plans.getEntries()) {
-            ((DefaultPlan) p).initialize(catalog, sourceURI);
+            ((DefaultPlan) p).initialize(catalog);
         }
     }
 
@@ -401,9 +396,6 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         if (catalogName != null ? !catalogName.equals(that.catalogName) : that.catalogName != null) {
             return false;
         }
-        if (catalogURI != null ? !catalogURI.equals(that.catalogURI) : that.catalogURI != null) {
-            return false;
-        }
         if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
             return false;
         }
@@ -442,7 +434,35 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         result = 31 * result + (planRules != null ? planRules.hashCode() : 0);
         result = 31 * result + (plans != null ? plans.hashCode() : 0);
         result = 31 * result + (priceLists != null ? priceLists.hashCode() : 0);
-        result = 31 * result + (catalogURI != null ? catalogURI.hashCode() : 0);
         return result;
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeObject(effectiveDate);
+        out.writeUTF(catalogName);
+        out.writeBoolean(recurringBillingMode != null);
+        if (recurringBillingMode != null) {
+            out.writeUTF(recurringBillingMode.name());
+        }
+        out.writeObject(supportedCurrencies);
+        out.writeObject(units);
+        out.writeObject(products);
+        out.writeObject(planRules);
+        out.writeObject(plans);
+        out.writeObject(priceLists);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.effectiveDate = (Date) in.readObject();
+        this.catalogName = in.readUTF();
+        this.recurringBillingMode = in.readBoolean() ? BillingMode.valueOf(in.readUTF()) : null;
+        this.supportedCurrencies = (Currency[]) in.readObject();
+        this.units = (DefaultUnit[]) in.readObject();
+        this.products = (CatalogEntityCollection<Product>) in.readObject();
+        this.planRules = (DefaultPlanRules) in.readObject();
+        this.plans = (CatalogEntityCollection<Plan>) in.readObject();
+        this.priceLists = (DefaultPriceListSet) in.readObject();
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
index 4729e6d..88b547f 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,10 @@
 
 package org.killbill.billing.catalog;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.util.regex.Matcher;
 
 import org.killbill.billing.ErrorCode;
@@ -25,7 +29,6 @@ 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.PlanPhase;
-import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.Product;
@@ -36,15 +39,19 @@ import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 
-public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implements StaticCatalog {
+public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implements StaticCatalog, Externalizable {
 
-    private final Long tenantRecordId;
+    private Long tenantRecordId;
 
     /* Since we offer endpoints that attempt to serialize catalog objects, we need to explicitly tell Jackson to ignore those fields */
     @JsonIgnore
-    private final InternalCallContextFactory internalCallContextFactory;
+    private InternalCallContextFactory internalCallContextFactory;
     @JsonIgnore
-    private final PriceOverride priceOverride;
+    private PriceOverride priceOverride;
+
+    // Required for deserialization
+    public StandaloneCatalogWithPriceOverride() {
+    }
 
     public StandaloneCatalogWithPriceOverride(final StandaloneCatalog catalog, final PriceOverride priceOverride, final Long tenantRecordId, final InternalCallContextFactory internalCallContextFactory) {
         // Initialize from input catalog
@@ -133,4 +140,22 @@ public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implem
     private InternalTenantContext createInternalTenantContext() {
         return internalCallContextFactory.createInternalTenantContext(tenantRecordId, null);
     }
+
+    public void initialize(final StandaloneCatalog catalog, final PriceOverride priceOverride, final InternalCallContextFactory internalCallContextFactory) {
+        super.initialize(catalog);
+        this.priceOverride = priceOverride;
+        this.internalCallContextFactory = internalCallContextFactory;
+    }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        super.writeExternal(out);
+        out.writeLong(tenantRecordId);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        super.readExternal(in);
+        this.tenantRecordId = in.readLong();
+    }
 }
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLWriter.java b/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLWriter.java
index de3d3da..87627b9 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLWriter.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLWriter.java
@@ -71,7 +71,7 @@ public class TestXMLWriter extends CatalogTestSuiteNoDB {
         final String oldCatalogStr = XMLWriter.writeXML(catalog, StandaloneCatalog.class);
         //System.err.println(oldCatalogStr);
 
-        final StandaloneCatalog oldCatalog = XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(oldCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
+        final StandaloneCatalog oldCatalog = XMLLoader.getObjectFromStream(new ByteArrayInputStream(oldCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
         final String oldCatalogStr2 = XMLWriter.writeXML(oldCatalog, StandaloneCatalog.class);
         assertEquals(oldCatalogStr2, oldCatalogStr);
     }
@@ -85,7 +85,7 @@ public class TestXMLWriter extends CatalogTestSuiteNoDB {
         final DefaultProduct newProduct = new DefaultProduct();
         newProduct.setName("Dynamic");
         newProduct.setCatagory(ProductCategory.BASE);
-        newProduct.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
+        newProduct.initialize((StandaloneCatalog) mutableCatalog);
 
         mutableCatalog.addProduct(newProduct);
 
@@ -108,10 +108,10 @@ public class TestXMLWriter extends CatalogTestSuiteNoDB {
         newPlan.setRecurringBillingMode(BillingMode.IN_ADVANCE);
         // TODO Ordering breaks
         mutableCatalog.addPlan(newPlan);
-        newPlan.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
+        newPlan.initialize((StandaloneCatalog) mutableCatalog);
 
         final String newCatalogStr = XMLWriter.writeXML((StandaloneCatalog) mutableCatalog, StandaloneCatalog.class);
-        final StandaloneCatalog newCatalog = XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
+        final StandaloneCatalog newCatalog = XMLLoader.getObjectFromStream(new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
         assertEquals(newCatalog.getCurrentPlans().size(), catalog.getCurrentPlans().size() + 1);
 
         final Plan plan = newCatalog.findCurrentPlan("dynamic-monthly");
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestCatalogPluginMapping.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestCatalogPluginMapping.java
index ed37579..2fb5c63 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestCatalogPluginMapping.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestCatalogPluginMapping.java
@@ -48,7 +48,7 @@ public class TestCatalogPluginMapping extends CatalogTestSuiteNoDB {
 
         final StandaloneCatalogMapper mapper = new StandaloneCatalogMapper(inputCatalog.getCatalogName());
 
-        final StandaloneCatalog output = mapper.toStandaloneCatalog(pluginCatalog, inputCatalog.getCatalogURI());
+        final StandaloneCatalog output = mapper.toStandaloneCatalog(pluginCatalog);
         output.setRecurringBillingMode(inputCatalog.getRecurringBillingMode());
         Assert.assertEquals(output, inputCatalog);
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
index 448942f..5ac2eb0 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
@@ -60,7 +60,7 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
 
         final CatalogUpdater catalogUpdater = new CatalogUpdater(now, null);
         final String catalogXML = catalogUpdater.getCatalogXML();
-        final StandaloneCatalog catalog = XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(catalogXML.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
+        final StandaloneCatalog catalog = XMLLoader.getObjectFromStream(new ByteArrayInputStream(catalogXML.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
         assertEquals(catalog.getCurrentPlans().size(), 0);
     }
 
@@ -294,7 +294,7 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
         final DefaultProduct newProduct = new DefaultProduct();
         newProduct.setName("Something");
         newProduct.setCatagory(ProductCategory.BASE);
-        newProduct.initialize((StandaloneCatalog) mutableCatalog, null);
+        newProduct.initialize((StandaloneCatalog) mutableCatalog);
         mutableCatalog.addProduct(newProduct);
 
         final DefaultPlanPhase trialPhase = new DefaultPlanPhase();
@@ -320,10 +320,10 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
         newPlan.setInitialPhases(new DefaultPlanPhase[]{trialPhase, fixedTermPhase});
         newPlan.setFinalPhase(fixedTermPhase);
         mutableCatalog.addPlan(newPlan);
-        newPlan.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
+        newPlan.initialize((StandaloneCatalog) mutableCatalog);
 
         final String newCatalogStr = XMLWriter.writeXML((StandaloneCatalog) mutableCatalog, StandaloneCatalog.class);
-        final StandaloneCatalog newCatalog = XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
+        final StandaloneCatalog newCatalog = XMLLoader.getObjectFromStream(new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
 
         final DefaultPlan targetPlan = newCatalog.findCurrentPlan("something-with-fixed-term");
         Assert.assertEquals(targetPlan.getInitialPhases().length, 2);
@@ -588,7 +588,7 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
         final DefaultProduct newProduct1 = new DefaultProduct();
         newProduct1.setName("Dynamic");
         newProduct1.setCatagory(ProductCategory.BASE);
-        newProduct1.initialize((StandaloneCatalog) mutableCatalog, null);
+        newProduct1.initialize((StandaloneCatalog) mutableCatalog);
         mutableCatalog.addProduct(newProduct1);
 
         final DefaultPlanPhase discountPhase1 = new DefaultPlanPhase();
@@ -609,12 +609,12 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
         newPlan1.setInitialPhases(new DefaultPlanPhase[]{discountPhase1});
         newPlan1.setFinalPhase(evergreenPhase1);
         mutableCatalog.addPlan(newPlan1);
-        newPlan1.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
+        newPlan1.initialize((StandaloneCatalog) mutableCatalog);
 
         final DefaultProduct newProduct2 = new DefaultProduct();
         newProduct2.setName("SuperDynamic");
         newProduct2.setCatagory(ProductCategory.BASE);
-        newProduct2.initialize((StandaloneCatalog) mutableCatalog, null);
+        newProduct2.initialize((StandaloneCatalog) mutableCatalog);
         mutableCatalog.addProduct(newProduct2);
 
         // Add a Plan with a FIXEDTERM phase
@@ -629,10 +629,10 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
         newPlan2.setProduct(newProduct2);
         newPlan2.setFinalPhase(fixedterm2);
         mutableCatalog.addPlan(newPlan2);
-        newPlan2.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
+        newPlan2.initialize((StandaloneCatalog) mutableCatalog);
 
         final String newCatalogStr = XMLWriter.writeXML((StandaloneCatalog) mutableCatalog, StandaloneCatalog.class);
-        return XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
+        return XMLLoader.getObjectFromStream(new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
     }
 
     private void addBadSimplePlanDescriptor(final CatalogUpdater catalogUpdater, final SimplePlanDescriptor desc) {
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java b/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java
index 46ba4da..273df5d 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestDefaultPriceOverride.java
@@ -52,7 +52,7 @@ public class TestDefaultPriceOverride extends CatalogTestSuiteWithEmbeddedDB {
     public void testBasic() throws Exception {
 
         final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
-        catalog.initialize(catalog, null);
+        catalog.initialize(catalog);
         final Plan plan = catalog.findCurrentPlan("discount-standard-monthly");
 
         final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
@@ -106,7 +106,7 @@ public class TestDefaultPriceOverride extends CatalogTestSuiteWithEmbeddedDB {
     public void testWithInvalidPriceOverride() throws Exception {
 
         final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
-        catalog.initialize(catalog, null);
+        catalog.initialize(catalog);
 
         final Plan plan = catalog.findCurrentPlan("discount-standard-monthly");
 
@@ -122,7 +122,7 @@ public class TestDefaultPriceOverride extends CatalogTestSuiteWithEmbeddedDB {
     public void testGetOverriddenPlan() throws Exception {
 
         final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
-        catalog.initialize(catalog, null);
+        catalog.initialize(catalog);
 
         final Plan plan = catalog.findCurrentPlan("discount-standard-monthly");
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestInternationalPrice.java b/catalog/src/test/java/org/killbill/billing/catalog/TestInternationalPrice.java
index 103777a..1f848c0 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestInternationalPrice.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestInternationalPrice.java
@@ -35,7 +35,7 @@ public class TestInternationalPrice extends CatalogTestSuiteNoDB {
         c.setSupportedCurrencies(new Currency[]{Currency.GBP, Currency.EUR, Currency.USD, Currency.BRL, Currency.MXN});
         final DefaultInternationalPrice p0 = new MockInternationalPrice();
         p0.setPrices(new DefaultPrice[0]);
-        p0.initialize(c, new URI("foo:bar"));
+        p0.initialize(c);
         final DefaultInternationalPrice p1 = new MockInternationalPrice();
         p1.setPrices(new DefaultPrice[]{
                 new DefaultPrice().setCurrency(Currency.GBP).setValue(new BigDecimal(1)),
@@ -44,7 +44,7 @@ public class TestInternationalPrice extends CatalogTestSuiteNoDB {
                 new DefaultPrice().setCurrency(Currency.BRL).setValue(new BigDecimal(1)),
                 new DefaultPrice().setCurrency(Currency.MXN).setValue(new BigDecimal(1)),
         });
-        p1.initialize(c, new URI("foo:bar"));
+        p1.initialize(c);
 
         Assert.assertEquals(p0.getPrice(Currency.GBP), new BigDecimal(0));
         Assert.assertEquals(p0.getPrice(Currency.EUR), new BigDecimal(0));
@@ -65,7 +65,7 @@ public class TestInternationalPrice extends CatalogTestSuiteNoDB {
         c.setSupportedCurrencies(new Currency[]{Currency.GBP, Currency.EUR, Currency.USD, Currency.BRL, Currency.MXN});
         ((DefaultInternationalPrice) c.getCurrentPlans().iterator().next().getFinalPhase().getRecurring().getRecurringPrice()).setPrices(new DefaultPrice[0]);
         c.setUnits(new DefaultUnit[0]);
-        c.initialize(c, new URI("foo://bar"));
+        c.initialize(c);
         Assert.assertEquals(c.getCurrentPlans().iterator().next().getFinalPhase().getRecurring().getRecurringPrice().getPrice(Currency.GBP), new BigDecimal(0));
     }
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestPlanPhase.java b/catalog/src/test/java/org/killbill/billing/catalog/TestPlanPhase.java
index df1663c..046fed0 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestPlanPhase.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestPlanPhase.java
@@ -33,14 +33,14 @@ public class TestPlanPhase extends CatalogTestSuiteNoDB {
         final MockCatalog catalog = new MockCatalog();
 
         DefaultPlanPhase pp = MockPlanPhase.createUSDMonthlyEvergreen(null, "1.00").setPlan(MockPlan.createBicycleNoTrialEvergreen1USD());
-        pp.initialize(catalog, null);
+        pp.initialize(catalog);
 
         ValidationErrors errors = pp.validate(catalog, new ValidationErrors());
         errors.log(log);
         Assert.assertEquals(errors.size(), 1);
 
         pp = MockPlanPhase.createUSDMonthlyEvergreen("1.00", null).setRecurring(new MockRecurring(BillingPeriod.NO_BILLING_PERIOD, MockInternationalPrice.createUSD("1.00")).setPhase(pp)).setPlan(MockPlan.createBicycleNoTrialEvergreen1USD());
-        pp.initialize(catalog, null);
+        pp.initialize(catalog);
         errors = pp.validate(catalog, new ValidationErrors());
         errors.log(log);
         Assert.assertEquals(errors.size(), 1);
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/OverdueResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/OverdueResource.java
index 6b6009a..426f23d 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/OverdueResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/OverdueResource.java
@@ -142,7 +142,7 @@ public class OverdueResource extends JaxRsResourceBase {
                                                    @javax.ws.rs.core.Context final UriInfo uriInfo) throws Exception {
         // Validation purpose:  Will throw if bad XML or catalog validation fails
         final InputStream stream = new ByteArrayInputStream(overdueXML.getBytes());
-        XMLLoader.getObjectFromStream(new URI(JaxrsResource.OVERDUE_PATH), stream, DefaultOverdueConfig.class);
+        XMLLoader.getObjectFromStream(stream, DefaultOverdueConfig.class);
 
         final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
         overdueApi.uploadOverdueConfig(overdueXML, callContext);
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java
index 3ae374c..242d86a 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,11 @@
 
 package org.killbill.billing.overdue.config;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
@@ -25,14 +30,14 @@ import javax.xml.bind.annotation.XmlElement;
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.joda.time.Period;
-
 import org.killbill.billing.catalog.api.Duration;
 import org.killbill.billing.catalog.api.TimeUnit;
 import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultDuration extends ValidatingConfig<DefaultOverdueConfig> implements Duration {
+public class DefaultDuration extends ValidatingConfig<DefaultOverdueConfig> implements Duration, Externalizable {
+
     @XmlElement(required = true)
     private TimeUnit unit;
 
@@ -66,7 +71,7 @@ public class DefaultDuration extends ValidatingConfig<DefaultOverdueConfig> impl
                 return dateTime.plusYears(number);
             case UNLIMITED:
             default:
-                throw new  IllegalStateException("Unexpected duration unit " + unit);
+                throw new IllegalStateException("Unexpected duration unit " + unit);
         }
     }
 
@@ -87,9 +92,10 @@ public class DefaultDuration extends ValidatingConfig<DefaultOverdueConfig> impl
                 return localDate.plusYears(number);
             case UNLIMITED:
             default:
-                throw new  IllegalStateException("Unexpected duration unit " + unit);
+                throw new IllegalStateException("Unexpected duration unit " + unit);
         }
     }
+
     @Override
     public Period toJodaPeriod() {
         if ((number == null) && (unit != TimeUnit.UNLIMITED)) {
@@ -107,7 +113,7 @@ public class DefaultDuration extends ValidatingConfig<DefaultOverdueConfig> impl
                 return new Period().withYears(number);
             case UNLIMITED:
             default:
-                throw new  IllegalStateException("Unexpected duration unit " + unit);
+                throw new IllegalStateException("Unexpected duration unit " + unit);
         }
     }
 
@@ -128,6 +134,30 @@ public class DefaultDuration extends ValidatingConfig<DefaultOverdueConfig> impl
     }
 
     @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultDuration that = (DefaultDuration) o;
+
+        if (unit != that.unit) {
+            return false;
+        }
+        return number != null ? number.equals(that.number) : that.number == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = unit != null ? unit.hashCode() : 0;
+        result = 31 * result + (number != null ? number.hashCode() : 0);
+        return result;
+    }
+
+    @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("DefaultDuration{");
         sb.append("unit=").append(unit);
@@ -135,4 +165,22 @@ public class DefaultDuration extends ValidatingConfig<DefaultOverdueConfig> impl
         sb.append('}');
         return sb.toString();
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeBoolean(unit != null);
+        if (unit != null) {
+            out.writeUTF(unit.name());
+        }
+        out.writeBoolean(number != null);
+        if (number != null) {
+            out.writeInt(number);
+        }
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.unit = in.readBoolean() ? TimeUnit.valueOf(in.readUTF()) : null;
+        this.number = in.readBoolean() ? in.readInt() : null;
+    }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueCondition.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueCondition.java
index a4e4b51..4a810b0 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueCondition.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueCondition.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,10 @@
 
 package org.killbill.billing.overdue.config;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.math.BigDecimal;
 import java.net.URI;
 import java.util.Arrays;
@@ -28,21 +32,20 @@ import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Duration;
 import org.killbill.billing.catalog.api.TimeUnit;
 import org.killbill.billing.overdue.ConditionEvaluation;
 import org.killbill.billing.overdue.api.OverdueCondition;
 import org.killbill.billing.overdue.config.api.BillingState;
 import org.killbill.billing.payment.api.PaymentResponse;
-import org.killbill.xmlloader.ValidatingConfig;
-import org.killbill.xmlloader.ValidationErrors;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 
-public class DefaultOverdueCondition extends ValidatingConfig<DefaultOverdueConfig> implements ConditionEvaluation, OverdueCondition {
+public class DefaultOverdueCondition extends ValidatingConfig<DefaultOverdueConfig> implements ConditionEvaluation, OverdueCondition, Externalizable {
 
     @XmlElement(required = false, name = "numberOfUnpaidInvoicesEqualsOrExceeds")
     private Integer numberOfUnpaidInvoicesEqualsOrExceeds;
@@ -115,19 +118,6 @@ public class DefaultOverdueCondition extends ValidatingConfig<DefaultOverdueConf
     }
 
     @Override
-    public void initialize(final DefaultOverdueConfig root, final URI uri) {
-    }
-
-    public Duration getTimeOffset() {
-        if (timeSinceEarliestUnpaidInvoiceEqualsOrExceeds != null) {
-            return timeSinceEarliestUnpaidInvoiceEqualsOrExceeds;
-        } else {
-            return new DefaultDuration().setUnit(TimeUnit.DAYS).setNumber(0); // zero time
-        }
-
-    }
-
-    @Override
     public Integer getNumberOfUnpaidInvoicesEqualsOrExceeds() {
         return numberOfUnpaidInvoicesEqualsOrExceeds;
     }
@@ -143,7 +133,7 @@ public class DefaultOverdueCondition extends ValidatingConfig<DefaultOverdueConf
     }
 
     @Override
-    public PaymentResponse [] getResponseForLastFailedPaymentIn() {
+    public PaymentResponse[] getResponseForLastFailedPaymentIn() {
         return responseForLastFailedPayment;
     }
 
@@ -182,6 +172,47 @@ public class DefaultOverdueCondition extends ValidatingConfig<DefaultOverdueConf
     }
 
     @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultOverdueCondition that = (DefaultOverdueCondition) o;
+
+        if (numberOfUnpaidInvoicesEqualsOrExceeds != null ? !numberOfUnpaidInvoicesEqualsOrExceeds.equals(that.numberOfUnpaidInvoicesEqualsOrExceeds) : that.numberOfUnpaidInvoicesEqualsOrExceeds != null) {
+            return false;
+        }
+        if (totalUnpaidInvoiceBalanceEqualsOrExceeds != null ? !totalUnpaidInvoiceBalanceEqualsOrExceeds.equals(that.totalUnpaidInvoiceBalanceEqualsOrExceeds) : that.totalUnpaidInvoiceBalanceEqualsOrExceeds != null) {
+            return false;
+        }
+        if (timeSinceEarliestUnpaidInvoiceEqualsOrExceeds != null ? !timeSinceEarliestUnpaidInvoiceEqualsOrExceeds.equals(that.timeSinceEarliestUnpaidInvoiceEqualsOrExceeds) : that.timeSinceEarliestUnpaidInvoiceEqualsOrExceeds != null) {
+            return false;
+        }
+        // Probably incorrect - comparing Object[] arrays with Arrays.equals
+        if (!Arrays.equals(responseForLastFailedPayment, that.responseForLastFailedPayment)) {
+            return false;
+        }
+        if (controlTagInclusion != that.controlTagInclusion) {
+            return false;
+        }
+        return controlTagExclusion == that.controlTagExclusion;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = numberOfUnpaidInvoicesEqualsOrExceeds != null ? numberOfUnpaidInvoicesEqualsOrExceeds.hashCode() : 0;
+        result = 31 * result + (totalUnpaidInvoiceBalanceEqualsOrExceeds != null ? totalUnpaidInvoiceBalanceEqualsOrExceeds.hashCode() : 0);
+        result = 31 * result + (timeSinceEarliestUnpaidInvoiceEqualsOrExceeds != null ? timeSinceEarliestUnpaidInvoiceEqualsOrExceeds.hashCode() : 0);
+        result = 31 * result + Arrays.hashCode(responseForLastFailedPayment);
+        result = 31 * result + (controlTagInclusion != null ? controlTagInclusion.hashCode() : 0);
+        result = 31 * result + (controlTagExclusion != null ? controlTagExclusion.hashCode() : 0);
+        return result;
+    }
+
+    @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("DefaultOverdueCondition{");
         sb.append("numberOfUnpaidInvoicesEqualsOrExceeds=").append(numberOfUnpaidInvoicesEqualsOrExceeds);
@@ -193,4 +224,33 @@ public class DefaultOverdueCondition extends ValidatingConfig<DefaultOverdueConf
         sb.append('}');
         return sb.toString();
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeBoolean(numberOfUnpaidInvoicesEqualsOrExceeds != null);
+        if (numberOfUnpaidInvoicesEqualsOrExceeds != null) {
+            out.writeInt(numberOfUnpaidInvoicesEqualsOrExceeds);
+        }
+        out.writeObject(totalUnpaidInvoiceBalanceEqualsOrExceeds);
+        out.writeObject(timeSinceEarliestUnpaidInvoiceEqualsOrExceeds);
+        out.writeObject(responseForLastFailedPayment);
+        out.writeBoolean(controlTagInclusion != null);
+        if (controlTagInclusion != null) {
+            out.writeUTF(controlTagInclusion.name());
+        }
+        out.writeBoolean(controlTagExclusion != null);
+        if (controlTagExclusion != null) {
+            out.writeUTF(controlTagExclusion.name());
+        }
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.numberOfUnpaidInvoicesEqualsOrExceeds = in.readBoolean() ? in.readInt() : null;
+        this.totalUnpaidInvoiceBalanceEqualsOrExceeds = (BigDecimal) in.readObject();
+        this.timeSinceEarliestUnpaidInvoiceEqualsOrExceeds = (DefaultDuration) in.readObject();
+        this.responseForLastFailedPayment = (PaymentResponse[]) in.readObject();
+        this.controlTagInclusion = in.readBoolean() ? ControlTagType.valueOf(in.readUTF()) : null;
+        this.controlTagExclusion = in.readBoolean() ? ControlTagType.valueOf(in.readUTF()) : null;
+    }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueConfig.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueConfig.java
index 0aec675..599d8d9 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueConfig.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueConfig.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,6 @@ import java.io.Externalizable;
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
-import java.net.URI;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -30,9 +29,6 @@ import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 
 import org.killbill.billing.overdue.api.OverdueConfig;
-import org.killbill.billing.util.cache.ExternalizableInput;
-import org.killbill.billing.util.cache.ExternalizableOutput;
-import org.killbill.billing.util.cache.MapperHolder;
 import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
 
@@ -63,17 +59,32 @@ public class DefaultOverdueConfig extends ValidatingConfig<DefaultOverdueConfig>
         return this;
     }
 
-    public URI getURI() {
-        return null;
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultOverdueConfig that = (DefaultOverdueConfig) o;
+
+        return accountOverdueStates != null ? accountOverdueStates.equals(that.accountOverdueStates) : that.accountOverdueStates == null;
+    }
+
+    @Override
+    public int hashCode() {
+        return accountOverdueStates != null ? accountOverdueStates.hashCode() : 0;
     }
 
     @Override
-    public void readExternal(final ObjectInput in) throws IOException {
-        MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.accountOverdueStates = (DefaultOverdueStatesAccount) in.readObject();
     }
 
     @Override
     public void writeExternal(final ObjectOutput oo) throws IOException {
-        MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+        oo.writeObject(accountOverdueStates);
     }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java
index 233a0ca..ca74058 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,21 +18,21 @@
 
 package org.killbill.billing.overdue.config;
 
-import java.util.List;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlAnyElement;
 import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlID;
 
-import org.joda.time.Period;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.catalog.api.Duration;
 import org.killbill.billing.catalog.api.TimeUnit;
 import org.killbill.billing.overdue.ConditionEvaluation;
-import org.killbill.billing.overdue.api.EmailNotification;
 import org.killbill.billing.overdue.api.OverdueApiException;
 import org.killbill.billing.overdue.api.OverdueCancellationPolicy;
 import org.killbill.billing.overdue.api.OverdueCondition;
@@ -42,7 +42,7 @@ import org.killbill.xmlloader.ValidationError;
 import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
-public class DefaultOverdueState extends ValidatingConfig<DefaultOverdueConfig> implements OverdueState {
+public class DefaultOverdueState extends ValidatingConfig<DefaultOverdueConfig> implements OverdueState, Externalizable {
 
     private static final int MAX_NAME_LENGTH = 50;
 
@@ -168,16 +168,66 @@ public class DefaultOverdueState extends ValidatingConfig<DefaultOverdueConfig> 
         return isClearState;
     }
 
-
     @Override
     public ValidationErrors validate(final DefaultOverdueConfig root,
                                      final ValidationErrors errors) {
         if (name.length() > MAX_NAME_LENGTH) {
-            errors.add(new ValidationError(String.format("Name of state '%s' exceeds the maximum length of %d", name, MAX_NAME_LENGTH), root.getURI(), DefaultOverdueState.class, name));
+            errors.add(new ValidationError(String.format("Name of state '%s' exceeds the maximum length of %d", name, MAX_NAME_LENGTH), DefaultOverdueState.class, name));
         }
         return errors;
     }
 
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultOverdueState that = (DefaultOverdueState) o;
+
+        if (condition != null ? !condition.equals(that.condition) : that.condition != null) {
+            return false;
+        }
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (externalMessage != null ? !externalMessage.equals(that.externalMessage) : that.externalMessage != null) {
+            return false;
+        }
+        if (blockChanges != null ? !blockChanges.equals(that.blockChanges) : that.blockChanges != null) {
+            return false;
+        }
+        if (disableEntitlement != null ? !disableEntitlement.equals(that.disableEntitlement) : that.disableEntitlement != null) {
+            return false;
+        }
+        if (subscriptionCancellationPolicy != that.subscriptionCancellationPolicy) {
+            return false;
+        }
+        if (isClearState != null ? !isClearState.equals(that.isClearState) : that.isClearState != null) {
+            return false;
+        }
+        if (autoReevaluationInterval != null ? !autoReevaluationInterval.equals(that.autoReevaluationInterval) : that.autoReevaluationInterval != null) {
+            return false;
+        }
+        return enterStateEmailNotification != null ? enterStateEmailNotification.equals(that.enterStateEmailNotification) : that.enterStateEmailNotification == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = condition != null ? condition.hashCode() : 0;
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (externalMessage != null ? externalMessage.hashCode() : 0);
+        result = 31 * result + (blockChanges != null ? blockChanges.hashCode() : 0);
+        result = 31 * result + (disableEntitlement != null ? disableEntitlement.hashCode() : 0);
+        result = 31 * result + (subscriptionCancellationPolicy != null ? subscriptionCancellationPolicy.hashCode() : 0);
+        result = 31 * result + (isClearState != null ? isClearState.hashCode() : 0);
+        result = 31 * result + (autoReevaluationInterval != null ? autoReevaluationInterval.hashCode() : 0);
+        result = 31 * result + (enterStateEmailNotification != null ? enterStateEmailNotification.hashCode() : 0);
+        return result;
+    }
 
     @Override
     public String toString() {
@@ -193,4 +243,31 @@ public class DefaultOverdueState extends ValidatingConfig<DefaultOverdueConfig> 
         sb.append('}');
         return sb.toString();
     }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeObject(condition);
+        out.writeUTF(name);
+        out.writeUTF(externalMessage);
+        out.writeBoolean(blockChanges);
+        out.writeBoolean(disableEntitlement);
+        out.writeBoolean(subscriptionCancellationPolicy != null);
+        if (subscriptionCancellationPolicy != null) {
+            out.writeUTF(subscriptionCancellationPolicy.name());
+        }
+        out.writeBoolean(isClearState);
+        out.writeObject(autoReevaluationInterval);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.condition = (DefaultOverdueCondition) in.readObject();
+        this.name = in.readUTF();
+        this.externalMessage = in.readUTF();
+        this.blockChanges = in.readBoolean();
+        this.disableEntitlement = in.readBoolean();
+        this.subscriptionCancellationPolicy = in.readBoolean() ? OverdueCancellationPolicy.valueOf(in.readUTF()) : null;
+        this.isClearState = in.readBoolean();
+        this.autoReevaluationInterval = (DefaultDuration) in.readObject();
+    }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStatesAccount.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStatesAccount.java
index c384444..e8e118e 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStatesAccount.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStatesAccount.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,17 +18,19 @@
 
 package org.killbill.billing.overdue.config;
 
-import java.util.List;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Arrays;
 
-import javax.xml.bind.annotation.XmlAnyElement;
 import javax.xml.bind.annotation.XmlElement;
 
 import org.joda.time.Period;
-
 import org.killbill.billing.catalog.api.TimeUnit;
 import org.killbill.billing.overdue.api.OverdueStatesAccount;
 
-public class DefaultOverdueStatesAccount extends DefaultOverdueStateSet implements OverdueStatesAccount {
+public class DefaultOverdueStatesAccount extends DefaultOverdueStateSet implements OverdueStatesAccount, Externalizable {
 
     @XmlElement(required = false, name = "initialReevaluationInterval")
     private DefaultDuration initialReevaluationInterval;
@@ -35,6 +39,9 @@ public class DefaultOverdueStatesAccount extends DefaultOverdueStateSet implemen
     @XmlElement(required = true, name = "state")
     private DefaultOverdueState[] accountOverdueStates = new DefaultOverdueState[0];
 
+    // Required for deserialization
+    public DefaultOverdueStatesAccount() {
+    }
 
     @Override
     public DefaultOverdueState[] getStates() {
@@ -58,4 +65,40 @@ public class DefaultOverdueStatesAccount extends DefaultOverdueStateSet implemen
         this.initialReevaluationInterval = initialReevaluationInterval;
         return this;
     }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultOverdueStatesAccount that = (DefaultOverdueStatesAccount) o;
+
+        if (initialReevaluationInterval != null ? !initialReevaluationInterval.equals(that.initialReevaluationInterval) : that.initialReevaluationInterval != null) {
+            return false;
+        }
+        return Arrays.equals(accountOverdueStates, that.accountOverdueStates);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = initialReevaluationInterval != null ? initialReevaluationInterval.hashCode() : 0;
+        result = 31 * result + Arrays.hashCode(accountOverdueStates);
+        return result;
+    }
+
+    @Override
+    public void writeExternal(final ObjectOutput out) throws IOException {
+        out.writeObject(initialReevaluationInterval);
+        out.writeObject(accountOverdueStates);
+    }
+
+    @Override
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.initialReevaluationInterval = (DefaultDuration) in.readObject();
+        this.accountOverdueStates = (DefaultOverdueState[]) in.readObject();
+    }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java
index af4a0cd..bfeff4c 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,7 +22,6 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 
 import org.joda.time.LocalDate;
-import org.joda.time.Period;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.overdue.api.OverdueApiException;
 import org.killbill.billing.overdue.api.OverdueState;
@@ -33,7 +34,6 @@ import org.killbill.xmlloader.ValidationErrors;
 @XmlAccessorType(XmlAccessType.NONE)
 public abstract class DefaultOverdueStateSet extends ValidatingConfig<DefaultOverdueConfig> implements OverdueStateSet {
 
-    private static final Period ZERO_PERIOD = new Period();
     private final DefaultOverdueState clearState = new DefaultOverdueState().setName(OverdueWrapper.CLEAR_STATE_NAME).setClearState(true);
 
     public abstract DefaultOverdueState[] getStates();
@@ -51,9 +51,6 @@ public abstract class DefaultOverdueStateSet extends ValidatingConfig<DefaultOve
         throw new OverdueApiException(ErrorCode.CAT_NO_SUCH_OVERDUE_STATE, stateName);
     }
 
-    /* (non-Javadoc)
-     * @see org.killbill.billing.catalog.overdue.OverdueBillingState#findClearState()
-     */
     @Override
     public DefaultOverdueState getClearState() throws OverdueApiException {
         return clearState;
@@ -80,7 +77,7 @@ public abstract class DefaultOverdueStateSet extends ValidatingConfig<DefaultOve
         } catch (OverdueApiException e) {
             if (e.getCode() == ErrorCode.CAT_MISSING_CLEAR_STATE.getCode()) {
                 errors.add("Overdue state set is missing a clear state.",
-                           root.getURI(), this.getClass(), "");
+                           this.getClass(), "");
             }
         }
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java b/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
index 9a00618..3b509aa 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
@@ -23,7 +23,7 @@ import org.killbill.billing.overdue.OverdueProperties;
 import org.killbill.billing.overdue.OverdueService;
 import org.killbill.billing.overdue.api.DefaultOverdueApi;
 import org.killbill.billing.overdue.api.OverdueApi;
-import org.killbill.billing.overdue.caching.EhCacheOverdueConfigCache;
+import org.killbill.billing.overdue.caching.DefaultOverdueConfigCache;
 import org.killbill.billing.overdue.caching.OverdueCacheInvalidationCallback;
 import org.killbill.billing.overdue.caching.OverdueConfigCache;
 import org.killbill.billing.overdue.listener.OverdueListener;
@@ -88,7 +88,7 @@ public class DefaultOverdueModule extends KillBillModule implements OverdueModul
     }
 
     public void installOverdueConfigCache() {
-        bind(OverdueConfigCache.class).to(EhCacheOverdueConfigCache.class).asEagerSingleton();
+        bind(OverdueConfigCache.class).to(DefaultOverdueConfigCache.class).asEagerSingleton();
         bind(CacheInvalidationCallback.class).annotatedWith(Names.named(OVERDUE_INVALIDATION_CALLBACK)).to(OverdueCacheInvalidationCallback.class).asEagerSingleton();
     }
 }
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/caching/MockOverdueConfigCache.java b/overdue/src/test/java/org/killbill/billing/overdue/caching/MockOverdueConfigCache.java
index 7d88488..26b6d03 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/caching/MockOverdueConfigCache.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/caching/MockOverdueConfigCache.java
@@ -24,7 +24,7 @@ import org.killbill.billing.overdue.api.OverdueApiException;
 import org.killbill.billing.overdue.api.OverdueConfig;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 
-public class MockOverdueConfigCache extends EhCacheOverdueConfigCache implements OverdueConfigCache
+public class MockOverdueConfigCache extends DefaultOverdueConfigCache implements OverdueConfigCache
 {
 
     private OverdueConfig overwriteDefaultOverdueConfig;
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/config/io/TestConfig.java b/overdue/src/test/java/org/killbill/billing/overdue/config/io/TestConfig.java
index 1e16a8d..41ceda1 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/config/io/TestConfig.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/config/io/TestConfig.java
@@ -43,7 +43,7 @@ public class TestConfig extends OverdueTestSuiteNoDB {
         final String overdueConfigStr = XMLWriter.writeXML(overdueConfig, DefaultOverdueConfig.class);
 
         //System.err.println(overdueConfigStr);
-        final DefaultOverdueConfig overdueConfig2 = XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(overdueConfigStr.getBytes(Charset.forName("UTF-8"))), DefaultOverdueConfig.class);
+        final DefaultOverdueConfig overdueConfig2 = XMLLoader.getObjectFromStream(new ByteArrayInputStream(overdueConfigStr.getBytes(Charset.forName("UTF-8"))), DefaultOverdueConfig.class);
         final String overdueConfigStr2 = XMLWriter.writeXML(overdueConfig2, DefaultOverdueConfig.class);
         Assert.assertEquals(overdueConfigStr, overdueConfigStr2);
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
index d9d06b0..52d8c9d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
@@ -23,7 +23,6 @@ import javax.inject.Provider;
 import org.killbill.automaton.DefaultStateMachineConfig;
 import org.killbill.automaton.StateMachineConfig;
 import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
-import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.AdminPaymentApi;
 import org.killbill.billing.payment.api.DefaultAdminPaymentApi;
@@ -38,7 +37,7 @@ import org.killbill.billing.payment.api.PaymentService;
 import org.killbill.billing.payment.api.svcs.DefaultInvoicePaymentInternalApi;
 import org.killbill.billing.payment.bus.PaymentBusEventHandler;
 import org.killbill.billing.payment.config.MultiTenantPaymentConfig;
-import org.killbill.billing.payment.caching.EhCacheStateMachineConfigCache;
+import org.killbill.billing.payment.caching.DefaultStateMachineConfigCache;
 import org.killbill.billing.payment.caching.StateMachineConfigCache;
 import org.killbill.billing.payment.caching.StateMachineConfigCacheInvalidationCallback;
 import org.killbill.billing.payment.core.PaymentExecutors;
@@ -122,7 +121,7 @@ public class PaymentModule extends KillBillModule {
 
         bind(PaymentControlStateMachineHelper.class).asEagerSingleton();
 
-        bind(StateMachineConfigCache.class).to(EhCacheStateMachineConfigCache.class).asEagerSingleton();
+        bind(StateMachineConfigCache.class).to(DefaultStateMachineConfigCache.class).asEagerSingleton();
         bind(CacheInvalidationCallback.class).annotatedWith(Names.named(STATE_MACHINE_CONFIG_INVALIDATION_CALLBACK)).to(StateMachineConfigCacheInvalidationCallback.class).asEagerSingleton();
 
         bind(PaymentStateMachineHelper.class).asEagerSingleton();
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 db29761..ea2689c 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
@@ -97,7 +97,7 @@ public class TestStateMachineConfigCache extends PaymentTestSuiteNoDB {
             }
         });
 
-        // Verify the lookup for a non-cached tenant. No system config is set yet but EhCacheStateMachineConfigCache returns a default empty one
+        // Verify the lookup for a non-cached tenant. No system config is set yet but DefaultStateMachineConfigCache returns a default empty one
         final StateMachineConfig defaultStateMachineConfig = stateMachineConfigCache.getPaymentStateMachineConfig(pluginName, differentMultiTenantContext);
         Assert.assertNotNull(defaultStateMachineConfig);
 
@@ -114,7 +114,6 @@ public class TestStateMachineConfigCache extends PaymentTestSuiteNoDB {
         Assert.assertEquals(stateMachineConfigCache.getPaymentStateMachineConfig(UUID.randomUUID().toString(), multiTenantContext), defaultStateMachineConfig);
         final StateMachineConfig result = stateMachineConfigCache.getPaymentStateMachineConfig(pluginName, multiTenantContext);
         Assert.assertNotNull(result);
-        Assert.assertNotEquals(result, defaultStateMachineConfig);
         Assert.assertEquals(result.getStateMachines().length, 8);
 
         // Verify the lookup for another tenant

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 334f2f2..02d1b7e 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.142.4</version>
+        <version>0.142.6</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.20.3-SNAPSHOT</version>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
index d7ce566..5c9437f 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
@@ -32,21 +32,21 @@ import org.apache.shiro.authz.ModularRealmAuthorizer;
 import org.apache.shiro.cache.CacheManager;
 import org.apache.shiro.guice.web.ShiroWebModuleWith435;
 import org.apache.shiro.realm.Realm;
+import org.apache.shiro.session.mgt.DefaultSessionManager;
 import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.session.mgt.eis.SessionDAO;
 import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
 import org.apache.shiro.web.mgt.WebSecurityManager;
 import org.apache.shiro.web.util.WebUtils;
 import org.killbill.billing.jaxrs.resources.JaxrsResource;
 import org.killbill.billing.server.security.FirstSuccessfulStrategyWith540;
-import org.killbill.billing.server.security.KillBillWebSessionManager;
 import org.killbill.billing.server.security.KillbillJdbcTenantRealm;
 import org.killbill.billing.util.config.definition.RbacConfig;
 import org.killbill.billing.util.glue.EhcacheShiroManagerProvider;
-import org.killbill.billing.util.glue.JDBCSessionDaoProvider;
 import org.killbill.billing.util.glue.KillBillShiroModule;
 import org.killbill.billing.util.glue.RealmsFromShiroIniProvider;
-import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
+import org.killbill.billing.util.glue.SessionDAOProvider;
 import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
 import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
 import org.killbill.billing.util.security.shiro.realm.KillBillOktaRealm;
@@ -118,10 +118,10 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
     protected void bindSessionManager(final AnnotatedBindingBuilder<SessionManager> bind) {
         // Bypass the servlet container completely for session management and delegate it to Shiro.
         // The default session timeout is 30 minutes.
-        bind.to(KillBillWebSessionManager.class).asEagerSingleton();
+        bind.to(DefaultSessionManager.class).asEagerSingleton();
 
         // Magic provider to configure the session DAO
-        bind(JDBCSessionDao.class).toProvider(JDBCSessionDaoProvider.class).asEagerSingleton();
+        bind(SessionDAO.class).toProvider(SessionDAOProvider.class).asEagerSingleton();
     }
 
     public static final class CorsBasicHttpAuthenticationFilter extends BasicHttpAuthenticationFilter {
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java
index 7faf4f6..01c8960 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java
@@ -33,9 +33,6 @@ import org.apache.shiro.codec.Base64;
 import org.apache.shiro.codec.Hex;
 import org.apache.shiro.realm.jdbc.JdbcRealm;
 import org.apache.shiro.util.ByteSource;
-import org.killbill.billing.util.cache.ExternalizableInput;
-import org.killbill.billing.util.cache.ExternalizableOutput;
-import org.killbill.billing.util.cache.MapperHolder;
 import org.killbill.billing.util.config.definition.SecurityConfig;
 import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
 
@@ -133,12 +130,14 @@ public class KillbillJdbcTenantRealm extends JdbcRealm {
 
         @Override
         public void readExternal(final ObjectInput in) throws IOException {
-            MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+            this.bytes = new byte[in.readInt()];
+            in.read(this.bytes);
         }
 
         @Override
         public void writeExternal(final ObjectOutput oo) throws IOException {
-            MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+            oo.writeInt(bytes.length);
+            oo.write(bytes);
         }
     }
 }
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/CallbackServlet.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/CallbackServlet.java
index bf0535d..d95ec8d 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/CallbackServlet.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/CallbackServlet.java
@@ -98,6 +98,28 @@ public class CallbackServlet extends HttpServlet {
         }
     }
 
+    public void flakyAssertListenerStatus() {
+        // Bail early
+        if (isListenerFailed) {
+            log.error(listenerFailedMsg);
+            Assert.fail(listenerFailedMsg);
+        }
+
+        try {
+            isCompleted(DELAY);
+        } catch (final Exception e) {
+            log.warn("flakyAssertListenerStatus didn't complete", e);
+        }
+
+        // Ignore missed events
+        nextExpectedEvent.clear();
+
+        if (isListenerFailed) {
+            log.error(listenerFailedMsg);
+            Assert.fail(listenerFailedMsg);
+        }
+    }
+
     public synchronized void reset() {
         receivedCalls.set(0);
         forceToFail.set(false);
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 4adf0a7..ec17e3c 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
@@ -88,6 +88,10 @@ public class TestCache extends TestJaxrsBase {
         assertTrue(accountImmutableCache.isKeyInCache(accountRecordId));
         assertTrue(accountBcdCache.isKeyInCache(input.getAccountId()));
 
+        // Make sure all events have been fully processed
+        clock.addDays(1);
+        callbackServlet.assertListenerStatus();
+
         // invalidate caches per account level by accountId
         adminApi.invalidatesCacheByAccount(input.getAccountId(), requestOptions);
 
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 763036a..07448a0 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
@@ -322,7 +322,11 @@ public class TestJaxrsBase extends KillbillClient {
         // Register tenant for callback
         final String callback = callbackServer.getServletEndpoint();
         tenantApi.registerPushNotificationCallback(callback, requestOptions);
-        callbackServlet.assertListenerStatus();
+
+        // Use the flaky version... In the non-happy path, the catalog event sent during tenant creation is received
+        // before we had the chance to register our servlet. In this case, instead of 2 events, we will only see one
+        // and the flakyAssertListenerStatus will timeout but not fail the test
+        callbackServlet.flakyAssertListenerStatus();
 
         createdTenant.setApiSecret(apiSecret);
 
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenant.java b/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenant.java
index dbda74f..e9acf05 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenant.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenant.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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:
  *
@@ -27,9 +29,6 @@ import javax.annotation.Nullable;
 import org.joda.time.DateTime;
 import org.killbill.billing.tenant.dao.TenantModelDao;
 import org.killbill.billing.util.UUIDs;
-import org.killbill.billing.util.cache.ExternalizableInput;
-import org.killbill.billing.util.cache.ExternalizableOutput;
-import org.killbill.billing.util.cache.MapperHolder;
 
 public class DefaultTenant implements Tenant, Externalizable {
 
@@ -137,10 +136,10 @@ public class DefaultTenant implements Tenant, Externalizable {
         if (id != null ? !id.equals(that.id) : that.id != null) {
             return false;
         }
-        if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
+        if (createdDate != null ? createdDate.compareTo(that.createdDate) != 0 : that.createdDate != null) {
             return false;
         }
-        if (updatedDate != null ? !updatedDate.equals(that.updatedDate) : that.updatedDate != null) {
+        if (updatedDate != null ? updatedDate.compareTo(that.updatedDate) != 0 : that.updatedDate != null) {
             return false;
         }
         if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
@@ -165,11 +164,23 @@ public class DefaultTenant implements Tenant, Externalizable {
 
     @Override
     public void readExternal(final ObjectInput in) throws IOException {
-        MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+        this.id = new UUID(in.readLong(), in.readLong());
+        this.createdDate = new DateTime(in.readUTF());
+        this.updatedDate = new DateTime(in.readUTF());
+        this.externalKey = in.readBoolean() ? in.readUTF() : null;
+        this.apiKey = in.readUTF();
     }
 
     @Override
     public void writeExternal(final ObjectOutput oo) throws IOException {
-        MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+        oo.writeLong(id.getMostSignificantBits());
+        oo.writeLong(id.getLeastSignificantBits());
+        oo.writeUTF(createdDate.toString());
+        oo.writeUTF(updatedDate.toString());
+        oo.writeBoolean(externalKey != null);
+        if (externalKey != null) {
+            oo.writeUTF(externalKey);
+        }
+        oo.writeUTF(apiKey);
     }
 }
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/user/DefaultTenantConfigChangeInternalEvent.java b/tenant/src/main/java/org/killbill/billing/tenant/api/user/DefaultTenantConfigChangeInternalEvent.java
index f62e80d..9c03bf7 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/api/user/DefaultTenantConfigChangeInternalEvent.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/api/user/DefaultTenantConfigChangeInternalEvent.java
@@ -57,4 +57,41 @@ public class DefaultTenantConfigChangeInternalEvent extends BusEventBase impleme
     public BusInternalEventType getBusEventType() {
         return BusInternalEventType.TENANT_CONFIG_CHANGE;
     }
+
+    @Override
+    public String toString() {
+        final StringBuffer sb = new StringBuffer("DefaultTenantConfigChangeInternalEvent{");
+        sb.append("id=").append(id);
+        sb.append(", key='").append(key).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        final DefaultTenantConfigChangeInternalEvent that = (DefaultTenantConfigChangeInternalEvent) o;
+
+        if (id != null ? !id.equals(that.id) : that.id != null) {
+            return false;
+        }
+        return key != null ? key.equals(that.key) : that.key == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + (id != null ? id.hashCode() : 0);
+        result = 31 * result + (key != null ? key.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/audit/dao/AuditLogModelDao.java b/util/src/main/java/org/killbill/billing/util/audit/dao/AuditLogModelDao.java
index 0c67fb0..b8be85f 100644
--- a/util/src/main/java/org/killbill/billing/util/audit/dao/AuditLogModelDao.java
+++ b/util/src/main/java/org/killbill/billing/util/audit/dao/AuditLogModelDao.java
@@ -25,11 +25,9 @@ import java.io.ObjectOutput;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.killbill.billing.callcontext.DefaultCallContext;
 import org.killbill.billing.util.audit.AuditLog;
 import org.killbill.billing.util.audit.ChangeType;
-import org.killbill.billing.util.cache.ExternalizableInput;
-import org.killbill.billing.util.cache.ExternalizableOutput;
-import org.killbill.billing.util.cache.MapperHolder;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.dao.EntityAudit;
 import org.killbill.billing.util.dao.TableName;
@@ -43,7 +41,7 @@ public class AuditLogModelDao implements EntityModelDao<AuditLog>, Externalizabl
     private TableName tableName;
     private Long targetRecordId;
     private ChangeType changeType;
-    private CallContext callContext;
+    private DefaultCallContext callContext;
 
     private Long recordId;
     private Long accountRecordId;
@@ -52,7 +50,7 @@ public class AuditLogModelDao implements EntityModelDao<AuditLog>, Externalizabl
     // For deserialization
     public AuditLogModelDao() {}
 
-    public AuditLogModelDao(final EntityAudit entityAudit, final CallContext callContext) {
+    public AuditLogModelDao(final EntityAudit entityAudit, final DefaultCallContext callContext) {
         this.id = entityAudit.getId();
         this.tableName = entityAudit.getTableName();
         this.targetRecordId = entityAudit.getTargetRecordId();
@@ -188,12 +186,33 @@ public class AuditLogModelDao implements EntityModelDao<AuditLog>, Externalizabl
     }
 
     @Override
-    public void readExternal(final ObjectInput in) throws IOException {
-        MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        this.id = new UUID(in.readLong(), in.read());
+        this.createdDate = new DateTime(in.readUTF());
+        this.updatedDate = new DateTime(in.readUTF());
+        this.tableName = TableName.valueOf(in.readUTF());
+        this.targetRecordId = in.readLong();
+        this.changeType = ChangeType.valueOf(in.readUTF());
+        this.callContext = (DefaultCallContext) in.readObject();
+        this.recordId = in.readLong();
+        this.accountRecordId = in.readLong();
+        this.tenantRecordId = in.readLong();
     }
 
     @Override
     public void writeExternal(final ObjectOutput oo) throws IOException {
-        MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+        oo.writeLong(id.getMostSignificantBits());
+        oo.writeLong(id.getLeastSignificantBits());
+        oo.writeUTF(createdDate.toString());
+        oo.writeUTF(updatedDate.toString());
+        oo.writeUTF(tableName.name());
+        oo.writeLong(targetRecordId);
+        oo.writeUTF(changeType.name());
+
+        oo.writeObject(callContext);
+
+        oo.writeLong(recordId);
+        oo.writeLong(accountRecordId);
+        oo.writeLong(tenantRecordId);
     }
 }
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 2ce2b6f..ce32de0 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
@@ -61,7 +61,7 @@ public class CacheControllerDispatcherProvider implements Provider<CacheControll
             }
             Preconditions.checkState(!cache.isClosed(), "Cache '%s' should not be closed", cacheType.getCacheName());
 
-            final CacheController<Object, Object> ehCacheBasedCacheController = new EhCacheBasedCacheController<Object, Object>(cache, cacheLoader);
+            final CacheController<Object, Object> ehCacheBasedCacheController = new KillBillCacheController<Object, Object>(cache, cacheLoader);
             cacheControllers.put(cacheType, ehCacheBasedCacheController);
         }
 
diff --git a/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfig.java b/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfig.java
index 25a3f34..1dae3c9 100644
--- a/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfig.java
@@ -23,10 +23,6 @@ import java.io.ObjectInput;
 import java.io.ObjectOutput;
 import java.util.HashMap;
 
-import org.killbill.billing.util.cache.ExternalizableInput;
-import org.killbill.billing.util.cache.ExternalizableOutput;
-import org.killbill.billing.util.cache.MapperHolder;
-
 public class PerTenantConfig extends HashMap<String, String> implements Externalizable {
 
     private static final long serialVersionUID = 3887971108446630172L;
@@ -35,12 +31,21 @@ public class PerTenantConfig extends HashMap<String, String> implements External
     }
 
     @Override
-    public void readExternal(final ObjectInput in) throws IOException {
-        MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+    public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+        final int size = in.readInt();
+        for (int i = 0; i < size; i++) {
+            final Object key = in.readObject();
+            final Object value = in.readObject();
+            put(String.valueOf(key), value == null ? null : String.valueOf(value));
+        }
     }
 
     @Override
     public void writeExternal(final ObjectOutput oo) throws IOException {
-        MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+        oo.writeInt(size());
+        for (final String key : keySet()) {
+            oo.writeObject(key);
+            oo.writeObject(get(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 cb7c571..40b55b5 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
@@ -45,6 +45,8 @@ import org.killbill.billing.util.config.definition.EhCacheConfig;
 import org.skife.config.ConfigurationObjectFactory;
 
 import com.google.inject.multibindings.Multibinder;
+import com.google.inject.name.Names;
+import com.google.inject.util.Providers;
 
 public class CacheModule extends KillBillModule {
 
@@ -54,8 +56,8 @@ public class CacheModule extends KillBillModule {
 
     @Override
     protected void configure() {
-        final EhCacheConfig config = new ConfigurationObjectFactory(skifeConfigSource).build(EhCacheConfig.class);
-        bind(EhCacheConfig.class).toInstance(config);
+        final EhCacheConfig ehCacheConfig = new ConfigurationObjectFactory(skifeConfigSource).build(EhCacheConfig.class);
+        bind(EhCacheConfig.class).toInstance(ehCacheConfig);
 
         // EhCache specifics
         bind(CacheManager.class).toProvider(Eh107CacheManagerProvider.class).asEagerSingleton();
diff --git a/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java b/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java
index 222dc7f..ceb312c 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,16 +17,9 @@
 
 package org.killbill.billing.util.glue;
 
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.net.URL;
-
 import javax.cache.Cache;
 import javax.cache.CacheManager;
-import javax.cache.configuration.MutableConfiguration;
-
-import org.killbill.billing.util.config.definition.EhCacheConfig;
-import org.killbill.xmlloader.UriAccessor;
+import javax.cache.configuration.Configuration;
 
 import com.codahale.metrics.Metric;
 import com.codahale.metrics.MetricFilter;
@@ -40,28 +33,15 @@ abstract class CacheProviderBase {
 
     private final MetricRegistry metricRegistry;
 
-    final URL xmlConfigurationURL;
-
-    CacheProviderBase(final MetricRegistry metricRegistry, final EhCacheConfig cacheConfig) {
+    CacheProviderBase(final MetricRegistry metricRegistry) {
         this.metricRegistry = metricRegistry;
-
-        try {
-            xmlConfigurationURL = UriAccessor.toURL(cacheConfig.getCacheConfigLocation());
-        } catch (final IOException e) {
-            throw new RuntimeException(e);
-        } catch (final URISyntaxException e) {
-            throw new RuntimeException(e);
-        }
     }
 
-    <K, V> Cache<K, V> createCache(final CacheManager cacheManager, final String cacheName, final Class<K> keyType, final Class<V> valueType) {
+    <C extends Configuration> void createCache(final CacheManager cacheManager, final String cacheName, final C configuration) {
         // Make sure we start from a clean state - this is mainly useful for tests
         cacheManager.destroyCache(cacheName);
 
-        // All other configuration options come from the ehcache.xml
-        final MutableConfiguration<K, V> configuration = new MutableConfiguration<K, V>().setTypes(keyType, valueType)
-                                                                                         .setStoreByValue(false); // Store by reference to avoid copying large objects (e.g. catalog)
-        final Cache<K, V> cache = cacheManager.createCache(cacheName, configuration);
+        final Cache cache = cacheManager.createCache(cacheName, configuration);
         Preconditions.checkState(!cache.isClosed(), "Cache '%s' should not be closed", cacheName);
 
         // Re-create the metrics to support dynamically created caches (e.g. for Shiro)
@@ -72,7 +52,5 @@ abstract class CacheProviderBase {
             }
         });
         metricRegistry.register(PROP_METRIC_REG_JCACHE_STATISTICS, new JCacheGaugeSet());
-
-        return cache;
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/glue/Eh107CacheManagerProvider.java b/util/src/main/java/org/killbill/billing/util/glue/Eh107CacheManagerProvider.java
index d5de5cc..49bf278 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/Eh107CacheManagerProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/Eh107CacheManagerProvider.java
@@ -36,7 +36,7 @@ import org.slf4j.LoggerFactory;
 import com.codahale.metrics.MetricRegistry;
 
 // EhCache specific provider
-public class Eh107CacheManagerProvider extends CacheProviderBase implements Provider<CacheManager> {
+public class Eh107CacheManagerProvider extends EhCacheProviderBase implements Provider<CacheManager> {
 
     private static final Logger logger = LoggerFactory.getLogger(Eh107CacheManagerProvider.class);
     private static final EhcacheLoggingListener ehcacheLoggingListener = new EhcacheLoggingListener();
@@ -54,7 +54,7 @@ public class Eh107CacheManagerProvider extends CacheProviderBase implements Prov
     @Override
     public CacheManager get() {
         // JSR-107 registration, required for JMX integration
-        final CachingProvider cachingProvider = Caching.getCachingProvider();
+        final CachingProvider cachingProvider = Caching.getCachingProvider("org.ehcache.jsr107.EhcacheCachingProvider");
 
         CacheManager cacheManager;
         try {
diff --git a/util/src/main/java/org/killbill/billing/util/glue/EhCacheProviderBase.java b/util/src/main/java/org/killbill/billing/util/glue/EhCacheProviderBase.java
new file mode 100644
index 0000000..924e766
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/glue/EhCacheProviderBase.java
@@ -0,0 +1,55 @@
+/*
+ * 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
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.glue;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import javax.cache.CacheManager;
+import javax.cache.configuration.Configuration;
+import javax.cache.configuration.MutableConfiguration;
+
+import org.killbill.billing.util.config.definition.EhCacheConfig;
+import org.killbill.xmlloader.UriAccessor;
+
+import com.codahale.metrics.MetricRegistry;
+
+abstract class EhCacheProviderBase extends CacheProviderBase {
+
+    final URL xmlConfigurationURL;
+
+    EhCacheProviderBase(final MetricRegistry metricRegistry, final EhCacheConfig cacheConfig) {
+        super(metricRegistry);
+
+        try {
+            xmlConfigurationURL = UriAccessor.toURL(cacheConfig.getCacheConfigLocation());
+        } catch (final IOException e) {
+            throw new RuntimeException(e);
+        } catch (final URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    <K, V> void createCache(final CacheManager cacheManager, final String cacheName, final Class<K> keyType, final Class<V> valueType) {
+        // All other configuration options come from the ehcache.xml
+        final Configuration configuration = new MutableConfiguration<K, V>().setTypes(keyType, valueType)
+                                                                            .setStoreByValue(false); // Store by reference to avoid copying large objects (e.g. catalog)
+        super.createCache(cacheManager, cacheName, configuration);
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java b/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java
index 943450c..361fb8d 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java
@@ -36,7 +36,7 @@ import org.slf4j.LoggerFactory;
 
 import com.codahale.metrics.MetricRegistry;
 
-public class EhcacheShiroManagerProvider extends CacheProviderBase implements Provider<EhcacheShiroManager> {
+public class EhcacheShiroManagerProvider extends EhCacheProviderBase implements Provider<EhcacheShiroManager> {
 
     private final SecurityManager securityManager;
     private final CacheManager eh107CacheManager;
diff --git a/util/src/main/java/org/killbill/billing/util/glue/GlobalLockerModule.java b/util/src/main/java/org/killbill/billing/util/glue/GlobalLockerModule.java
index 6abfb5d..830b0dd 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/GlobalLockerModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/GlobalLockerModule.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
index 154bc1d..07a9613 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
@@ -21,14 +21,12 @@ package org.killbill.billing.util.glue;
 import org.apache.shiro.cache.CacheManager;
 import org.apache.shiro.guice.ShiroModule;
 import org.apache.shiro.mgt.SecurityManager;
-import org.apache.shiro.realm.Realm;
 import org.apache.shiro.realm.text.IniRealm;
 import org.apache.shiro.session.mgt.DefaultSessionManager;
 import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.session.mgt.eis.SessionDAO;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.config.definition.RbacConfig;
-import org.killbill.billing.util.config.definition.SecurityConfig;
-import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
 import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
 import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
 import org.killbill.billing.util.security.shiro.realm.KillBillOktaRealm;
@@ -123,6 +121,6 @@ public class KillBillShiroModule extends ShiroModule {
         bind.to(DefaultSessionManager.class).asEagerSingleton();
 
         // Magic provider to configure the session DAO
-        bind(JDBCSessionDao.class).toProvider(JDBCSessionDaoProvider.class).asEagerSingleton();
+        bind(SessionDAO.class).toProvider(SessionDAOProvider.class).asEagerSingleton();
     }
 }
diff --git a/util/src/test/java/org/killbill/billing/util/cache/TestKillBillCacheController.java b/util/src/test/java/org/killbill/billing/util/cache/TestKillBillCacheController.java
new file mode 100644
index 0000000..23d4f35
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/cache/TestKillBillCacheController.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.cache;
+
+import javax.cache.Cache;
+import javax.cache.CacheException;
+
+import org.killbill.billing.util.UtilTestSuiteNoDB;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestKillBillCacheController extends UtilTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testWithBrokenCache() {
+        final Cache cache = Mockito.mock(Cache.class, new Answer() {
+            @Override
+            public Object answer(final InvocationOnMock invocation) throws Throwable {
+                // e.g. Redis connection broken
+                throw new CacheException("Exception for testing");
+            }
+        });
+
+        final BaseCacheLoader<String, Long> baseCacheLoader = new BaseCacheLoader<String, Long>() {
+            @Override
+            public CacheType getCacheType() {
+                return CacheType.RECORD_ID;
+            }
+
+            @Override
+            public Long compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
+                return Long.valueOf(key);
+            }
+        };
+
+        final KillBillCacheController<String, Long> killBillCacheController = new KillBillCacheController<String, Long>(cache, baseCacheLoader);
+
+        try {
+            killBillCacheController.getKeys();
+            Assert.fail();
+        } catch (final CacheException e) {
+            // Nothing we can do
+        }
+
+        // This will go back to the cache loader
+        Assert.assertEquals(killBillCacheController.get("12", null), new Long(12));
+    }
+}