Repository: googleapis/java-storage
Pull request #1344 (2022-04-06 19:47:54)

chore(grpc): refactor model to/from Pb methods to be abstracted by Codec
* Introduce new internal API Codec<From, To> which provides a symmetric pair of conversion functions between two types
* Add ApiaryConversions.java to contain all the new Codecs for each of the existing apiary toPb/fromPb methods
* Update StorageImpl and associated classes to use new codecs rather than old model methods
* Add clirr rule to allow removing accidental public HmacKeyMetadata#toPb
* Add BucketInfo#asBucket to convert from a BucketInfo to the syntax class Bucket with a specified instance of Storage
* Add BlobInfo#asBlob to convert from a BlobInfo to the syntax class Blob with a specified instance of Storage
* Add Utils#ifNonNull to help tersify a lot of the conversion code in ApiaryConversions


BenWhitehead: Refactored the notification conversions as well, thanks for the reminder.

Ready for re-review.

Patch:
diff --git a/google-cloud-storage/clirr-ignored-differences.xml b/google-cloud-storage/clirr-ignored-differences.xml
index 8aabcf10..b8fdaa16 100644
--- a/google-cloud-storage/clirr-ignored-differences.xml
+++ b/google-cloud-storage/clirr-ignored-differences.xml
@@ -11,4 +11,9 @@
     <differenceType>7012</differenceType>
     <method>* downloadTo(com.google.cloud.storage.BlobId, java.nio.file.Path, com.google.cloud.storage.Storage$BlobSourceOption[])</method>
   </difference>
+  <difference>
+    <differenceType>7002</differenceType>
+    <className>com/google/cloud/storage/HmacKey$HmacKeyMetadata</className>
+    <method>* toPb()</method>
+  </difference>
 </differences>
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Acl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Acl.java
index acf2ae8e..40a1c608 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Acl.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Acl.java
@@ -19,11 +19,8 @@ package com.google.cloud.storage;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import com.google.api.core.ApiFunction;
-import com.google.api.services.storage.model.BucketAccessControl;
-import com.google.api.services.storage.model.ObjectAccessControl;
 import com.google.cloud.StringEnumType;
 import com.google.cloud.StringEnumValue;
-import com.google.common.base.Function;
 import com.google.common.base.MoreObjects;
 import java.io.Serializable;
 import java.util.Objects;
@@ -37,20 +34,6 @@ import java.util.Objects;
 public final class Acl implements Serializable {
 
   private static final long serialVersionUID = 7516713233557576082L;
-  static final Function<ObjectAccessControl, Acl> FROM_OBJECT_PB_FUNCTION =
-      new Function<ObjectAccessControl, Acl>() {
-        @Override
-        public Acl apply(ObjectAccessControl aclPb) {
-          return Acl.fromPb(aclPb);
-        }
-      };
-  static final Function<BucketAccessControl, Acl> FROM_BUCKET_PB_FUNCTION =
-      new Function<BucketAccessControl, Acl>() {
-        @Override
-        public Acl apply(BucketAccessControl aclPb) {
-          return Acl.fromPb(aclPb);
-        }
-      };
 
   private final Entity entity;
   private final Role role;
@@ -64,15 +47,9 @@ public final class Acl implements Serializable {
       super(constant);
     }
 
-    private static final ApiFunction<String, Role> CONSTRUCTOR =
-        new ApiFunction<String, Role>() {
-          @Override
-          public Role apply(String constant) {
-            return new Role(constant);
-          }
-        };
+    private static final ApiFunction<String, Role> CONSTRUCTOR = Role::new;
 
-    private static final StringEnumType<Role> type = new StringEnumType(Role.class, CONSTRUCTOR);
+    private static final StringEnumType<Role> type = new StringEnumType<>(Role.class, CONSTRUCTOR);
 
     public static final Role OWNER = type.createAndRegister("OWNER");
     public static final Role READER = type.createAndRegister("READER");
@@ -195,36 +172,7 @@ public final class Acl implements Serializable {
 
     @Override
     public String toString() {
-      return toPb();
-    }
-
-    String toPb() {
-      return type.name().toLowerCase() + "-" + getValue();
-    }
-
-    static Entity fromPb(String entity) {
-      if (entity.startsWith("user-")) {
-        return new User(entity.substring(5));
-      }
-      if (entity.equals(User.ALL_USERS)) {
-        return User.ofAllUsers();
-      }
-      if (entity.equals(User.ALL_AUTHENTICATED_USERS)) {
-        return User.ofAllAuthenticatedUsers();
-      }
-      if (entity.startsWith("group-")) {
-        return new Group(entity.substring(6));
-      }
-      if (entity.startsWith("domain-")) {
-        return new Domain(entity.substring(7));
-      }
-      if (entity.startsWith("project-")) {
-        int idx = entity.indexOf('-', 8);
-        String team = entity.substring(8, idx);
-        String projectId = entity.substring(idx + 1);
-        return new Project(Project.ProjectRole.valueOf(team.toUpperCase()), projectId);
-      }
-      return new RawEntity(entity);
+      return Conversions.apiary().entity().encode(this);
     }
   }
 
@@ -272,8 +220,8 @@ public final class Acl implements Serializable {
   public static final class User extends Entity {
 
     private static final long serialVersionUID = 3076518036392737008L;
-    private static final String ALL_USERS = "allUsers";
-    private static final String ALL_AUTHENTICATED_USERS = "allAuthenticatedUsers";
+    static final String ALL_USERS = "allUsers";
+    static final String ALL_AUTHENTICATED_USERS = "allAuthenticatedUsers";
 
     /**
      * Creates a user entity.
@@ -289,19 +237,6 @@ public final class Acl implements Serializable {
       return getValue();
     }
 
-    @Override
-    String toPb() {
-      switch (getValue()) {
-        case ALL_AUTHENTICATED_USERS:
-          return ALL_AUTHENTICATED_USERS;
-        case ALL_USERS:
-          return ALL_USERS;
-        default:
-          break;
-      }
-      return super.toPb();
-    }
-
     public static User ofAllUsers() {
       return new User(ALL_USERS);
     }
@@ -326,16 +261,10 @@ public final class Acl implements Serializable {
         super(constant);
       }
 
-      private static final ApiFunction<String, ProjectRole> CONSTRUCTOR =
-          new ApiFunction<String, ProjectRole>() {
-            @Override
-            public ProjectRole apply(String constant) {
-              return new ProjectRole(constant);
-            }
-          };
+      private static final ApiFunction<String, ProjectRole> CONSTRUCTOR = ProjectRole::new;
 
       private static final StringEnumType<ProjectRole> type =
-          new StringEnumType(ProjectRole.class, CONSTRUCTOR);
+          new StringEnumType<>(ProjectRole.class, CONSTRUCTOR);
 
       public static final ProjectRole OWNERS = type.createAndRegister("OWNERS");
       public static final ProjectRole EDITORS = type.createAndRegister("EDITORS");
@@ -390,11 +319,6 @@ public final class Acl implements Serializable {
     RawEntity(String entity) {
       super(Type.UNKNOWN, entity);
     }
-
-    @Override
-    String toPb() {
-      return getValue();
-    }
   }
 
   private Acl(Builder builder) {
@@ -482,40 +406,4 @@ public final class Acl implements Serializable {
         && Objects.equals(this.etag, other.etag)
         && Objects.equals(this.id, other.id);
   }
-
-  BucketAccessControl toBucketPb() {
-    BucketAccessControl bucketPb = new BucketAccessControl();
-    bucketPb.setEntity(getEntity().toString());
-    bucketPb.setRole(getRole().toString());
-    bucketPb.setId(getId());
-    bucketPb.setEtag(getEtag());
-    return bucketPb;
-  }
-
-  ObjectAccessControl toObjectPb() {
-    ObjectAccessControl objectPb = new ObjectAccessControl();
-    objectPb.setEntity(getEntity().toPb());
-    objectPb.setRole(getRole().name());
-    objectPb.setId(getId());
-    objectPb.setEtag(getEtag());
-    return objectPb;
-  }
-
-  static Acl fromPb(ObjectAccessControl objectAccessControl) {
-    Role role = Role.valueOf(objectAccessControl.getRole());
-    Entity entity = Entity.fromPb(objectAccessControl.getEntity());
-    return newBuilder(entity, role)
-        .setEtag(objectAccessControl.getEtag())
-        .setId(objectAccessControl.getId())
-        .build();
-  }
-
-  static Acl fromPb(BucketAccessControl bucketAccessControl) {
-    Role role = Role.valueOf(bucketAccessControl.getRole());
-    Entity entity = Entity.fromPb(bucketAccessControl.getEntity());
-    return newBuilder(entity, role)
-        .setEtag(bucketAccessControl.getEtag())
-        .setId(bucketAccessControl.getId())
-        .build();
-  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
new file mode 100644
index 00000000..3d2c446d
--- /dev/null
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
@@ -0,0 +1,816 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed 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 com.google.cloud.storage;
+
+import static com.google.cloud.storage.Utils.ifNonNull;
+import static com.google.cloud.storage.Utils.lift;
+import static com.google.common.base.MoreObjects.firstNonNull;
+
+import com.google.api.client.util.Data;
+import com.google.api.client.util.DateTime;
+import com.google.api.core.InternalApi;
+import com.google.api.services.storage.model.Bucket;
+import com.google.api.services.storage.model.Bucket.Billing;
+import com.google.api.services.storage.model.Bucket.Encryption;
+import com.google.api.services.storage.model.Bucket.IamConfiguration.UniformBucketLevelAccess;
+import com.google.api.services.storage.model.Bucket.Lifecycle;
+import com.google.api.services.storage.model.Bucket.Lifecycle.Rule;
+import com.google.api.services.storage.model.Bucket.Lifecycle.Rule.Action;
+import com.google.api.services.storage.model.Bucket.Lifecycle.Rule.Condition;
+import com.google.api.services.storage.model.Bucket.RetentionPolicy;
+import com.google.api.services.storage.model.Bucket.Versioning;
+import com.google.api.services.storage.model.Bucket.Website;
+import com.google.api.services.storage.model.BucketAccessControl;
+import com.google.api.services.storage.model.ObjectAccessControl;
+import com.google.api.services.storage.model.StorageObject;
+import com.google.api.services.storage.model.StorageObject.Owner;
+import com.google.cloud.storage.Acl.Domain;
+import com.google.cloud.storage.Acl.Entity;
+import com.google.cloud.storage.Acl.Group;
+import com.google.cloud.storage.Acl.Project;
+import com.google.cloud.storage.Acl.RawEntity;
+import com.google.cloud.storage.Acl.Role;
+import com.google.cloud.storage.Acl.User;
+import com.google.cloud.storage.BlobInfo.CustomerEncryption;
+import com.google.cloud.storage.BucketInfo.AgeDeleteRule;
+import com.google.cloud.storage.BucketInfo.BuilderImpl;
+import com.google.cloud.storage.BucketInfo.CreatedBeforeDeleteRule;
+import com.google.cloud.storage.BucketInfo.DeleteRule;
+import com.google.cloud.storage.BucketInfo.IamConfiguration;
+import com.google.cloud.storage.BucketInfo.IsLiveDeleteRule;
+import com.google.cloud.storage.BucketInfo.LifecycleRule;
+import com.google.cloud.storage.BucketInfo.LifecycleRule.DeleteLifecycleAction;
+import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleAction;
+import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleCondition;
+import com.google.cloud.storage.BucketInfo.LifecycleRule.SetStorageClassLifecycleAction;
+import com.google.cloud.storage.BucketInfo.Logging;
+import com.google.cloud.storage.BucketInfo.NumNewerVersionsDeleteRule;
+import com.google.cloud.storage.BucketInfo.PublicAccessPrevention;
+import com.google.cloud.storage.BucketInfo.RawDeleteRule;
+import com.google.cloud.storage.Conversions.Codec;
+import com.google.cloud.storage.Cors.Origin;
+import com.google.cloud.storage.HmacKey.HmacKeyMetadata;
+import com.google.cloud.storage.HmacKey.HmacKeyState;
+import com.google.cloud.storage.NotificationInfo.EventType;
+import com.google.cloud.storage.NotificationInfo.PayloadFormat;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+@InternalApi
+final class ApiaryConversions {
+  static final ApiaryConversions INSTANCE = new ApiaryConversions();
+
+  private final Codec<Entity, String> entityCodec =
+      Codec.of(this::entityEncode, this::entityDecode);
+  private final Codec<Acl, ObjectAccessControl> objectAclCodec =
+      Codec.of(this::objectAclEncode, this::objectAclDecode);
+  private final Codec<Acl, BucketAccessControl> bucketAclCodec =
+      Codec.of(this::bucketAclEncode, this::bucketAclDecode);
+  private final Codec<HmacKeyMetadata, com.google.api.services.storage.model.HmacKeyMetadata>
+      hmacKeyMetadataCodec = Codec.of(this::hmacKeyMetadataEncode, this::hmacKeyMetadataDecode);
+  private final Codec<HmacKey, com.google.api.services.storage.model.HmacKey> hmacKeyCodec =
+      Codec.of(this::hmacKeyEncode, this::hmacKeyDecode);
+  private final Codec<ServiceAccount, com.google.api.services.storage.model.ServiceAccount>
+      serviceAccountCodec = Codec.of(this::serviceAccountEncode, this::serviceAccountDecode);
+  private final Codec<Cors, Bucket.Cors> corsCodec = Codec.of(this::corsEncode, this::corsDecode);
+  private final Codec<Logging, Bucket.Logging> loggingCodec =
+      Codec.of(this::loggingEncode, this::loggingDecode);
+  private final Codec<IamConfiguration, Bucket.IamConfiguration> iamConfigurationCodec =
+      Codec.of(this::iamConfigEncode, this::iamConfigDecode);
+  private final Codec<LifecycleRule, Rule> lifecycleRuleCodec =
+      Codec.of(this::lifecycleRuleEncode, this::lifecycleRuleDecode);
+
+  @SuppressWarnings("deprecation")
+  private final Codec<DeleteRule, Rule> deleteRuleCodec =
+      Codec.of(this::deleteRuleEncode, this::deleteRuleDecode);
+
+  private final Codec<BucketInfo, Bucket> bucketInfoCodec =
+      Codec.of(this::bucketInfoEncode, this::bucketInfoDecode);
+  private final Codec<CustomerEncryption, StorageObject.CustomerEncryption>
+      customerEncryptionCodec =
+          Codec.of(this::customerEncryptionEncode, this::customerEncryptionDecode);
+  private final Codec<BlobId, StorageObject> blobIdCodec =
+      Codec.of(this::blobIdEncode, this::blobIdDecode);
+  private final Codec<BlobInfo, StorageObject> blobInfoCodec =
+      Codec.of(this::blobInfoEncode, this::blobInfoDecode);
+
+  private final Codec<NotificationInfo, com.google.api.services.storage.model.Notification>
+      notificationInfoCodec = Codec.of(this::notificationEncode, this::notificationDecode);
+
+  private ApiaryConversions() {}
+
+  Codec<Entity, String> entity() {
+    return entityCodec;
+  }
+
+  Codec<Acl, ObjectAccessControl> objectAcl() {
+    return objectAclCodec;
+  }
+
+  Codec<Acl, BucketAccessControl> bucketAcl() {
+    return bucketAclCodec;
+  }
+
+  Codec<HmacKeyMetadata, com.google.api.services.storage.model.HmacKeyMetadata> hmacKeyMetadata() {
+    return hmacKeyMetadataCodec;
+  }
+
+  Codec<HmacKey, com.google.api.services.storage.model.HmacKey> hmacKey() {
+    return hmacKeyCodec;
+  }
+
+  Codec<ServiceAccount, com.google.api.services.storage.model.ServiceAccount> serviceAccount() {
+    return serviceAccountCodec;
+  }
+
+  Codec<Cors, Bucket.Cors> cors() {
+    return corsCodec;
+  }
+
+  Codec<Logging, Bucket.Logging> logging() {
+    return loggingCodec;
+  }
+
+  Codec<IamConfiguration, Bucket.IamConfiguration> iamConfiguration() {
+    return iamConfigurationCodec;
+  }
+
+  Codec<LifecycleRule, Rule> lifecycleRule() {
+    return lifecycleRuleCodec;
+  }
+
+  @SuppressWarnings("deprecation")
+  Codec<DeleteRule, Rule> deleteRule() {
+    return deleteRuleCodec;
+  }
+
+  Codec<BucketInfo, com.google.api.services.storage.model.Bucket> bucketInfo() {
+    return bucketInfoCodec;
+  }
+
+  Codec<CustomerEncryption, StorageObject.CustomerEncryption> customerEncryption() {
+    return customerEncryptionCodec;
+  }
+
+  Codec<BlobId, StorageObject> blobId() {
+    return blobIdCodec;
+  }
+
+  Codec<BlobInfo, StorageObject> blobInfo() {
+    return blobInfoCodec;
+  }
+
+  Codec<NotificationInfo, com.google.api.services.storage.model.Notification> notificationInfo() {
+    return notificationInfoCodec;
+  }
+
+  private StorageObject blobInfoEncode(BlobInfo from) {
+    StorageObject to = blobIdEncode(from.getBlobId());
+    ifNonNull(from.getAcl(), toImmutableListOf(objectAcl()::encode), to::setAcl);
+    ifNonNull(from.getDeleteTime(), DateTime::new, to::setTimeDeleted);
+    ifNonNull(from.getUpdateTime(), DateTime::new, to::setUpdated);
+    ifNonNull(from.getCreateTime(), DateTime::new, to::setTimeCreated);
+    ifNonNull(from.getCustomTime(), DateTime::new, to::setCustomTime);
+    ifNonNull(from.getSize(), BigInteger::valueOf, to::setSize);
+    ifNonNull(
+        from.getOwner(),
+        lift(this::entityEncode).andThen(o -> new Owner().setEntity(o)),
+        to::setOwner);
+    ifNonNull(from.getStorageClass(), StorageClass::toString, to::setStorageClass);
+    ifNonNull(from.getTimeStorageClassUpdated(), DateTime::new, to::setTimeStorageClassUpdated);
+    ifNonNull(
+        from.getCustomerEncryption(), this::customerEncryptionEncode, to::setCustomerEncryption);
+    ifNonNull(from.getRetentionExpirationTime(), DateTime::new, to::setRetentionExpirationTime);
+    to.setKmsKeyName(from.getKmsKeyName());
+    to.setEventBasedHold(from.getEventBasedHold());
+    to.setTemporaryHold(from.getTemporaryHold());
+    // Do not use, #getMetadata(), it can not return null, which is important to our logic here
+    Map<String, String> pbMetadata = from.metadata;
+    if (pbMetadata != null && !Data.isNull(pbMetadata)) {
+      pbMetadata = Maps.newHashMapWithExpectedSize(from.getMetadata().size());
+      for (Map.Entry<String, String> entry : from.getMetadata().entrySet()) {
+        pbMetadata.put(entry.getKey(), firstNonNull(entry.getValue(), Data.nullOf(String.class)));
+      }
+    }
+    to.setMetadata(pbMetadata);
+    to.setCacheControl(from.getCacheControl());
+    to.setContentEncoding(from.getContentEncoding());
+    to.setCrc32c(from.getCrc32c());
+    to.setContentType(from.getContentType());
+    to.setMd5Hash(from.getMd5());
+    to.setMediaLink(from.getMediaLink());
+    to.setMetageneration(from.getMetageneration());
+    to.setContentDisposition(from.getContentDisposition());
+    to.setComponentCount(from.getComponentCount());
+    to.setContentLanguage(from.getContentLanguage());
+    to.setEtag(from.getEtag());
+    to.setId(from.getGeneratedId());
+    to.setSelfLink(from.getSelfLink());
+    return to;
+  }
+
+  private BlobInfo blobInfoDecode(StorageObject from) {
+    BlobInfo.Builder to = BlobInfo.newBuilder(blobIdDecode(from));
+    ifNonNull(from.getCacheControl(), to::setCacheControl);
+    ifNonNull(from.getContentEncoding(), to::setContentEncoding);
+    ifNonNull(from.getCrc32c(), to::setCrc32c);
+    ifNonNull(from.getContentType(), to::setContentType);
+    ifNonNull(from.getMd5Hash(), to::setMd5);
+    ifNonNull(from.getMediaLink(), to::setMediaLink);
+    ifNonNull(from.getMetageneration(), to::setMetageneration);
+    ifNonNull(from.getContentDisposition(), to::setContentDisposition);
+    ifNonNull(from.getComponentCount(), to::setComponentCount);
+    ifNonNull(from.getContentLanguage(), to::setContentLanguage);
+    ifNonNull(from.getEtag(), to::setEtag);
+    ifNonNull(from.getId(), to::setGeneratedId);
+    ifNonNull(from.getSelfLink(), to::setSelfLink);
+    ifNonNull(from.getMetadata(), to::setMetadata);
+    ifNonNull(from.getTimeDeleted(), DateTime::getValue, to::setDeleteTime);
+    ifNonNull(from.getUpdated(), DateTime::getValue, to::setUpdateTime);
+    ifNonNull(from.getTimeCreated(), DateTime::getValue, to::setCreateTime);
+    ifNonNull(from.getCustomTime(), DateTime::getValue, to::setCustomTime);
+    ifNonNull(from.getSize(), BigInteger::longValue, to::setSize);
+    ifNonNull(from.getOwner(), lift(Owner::getEntity).andThen(this::entityDecode), to::setOwner);
+    ifNonNull(from.getAcl(), toImmutableListOf(objectAcl()::decode), to::setAcl);
+    if (from.containsKey("isDirectory")) {
+      to.setIsDirectory(Boolean.TRUE);
+    }
+    ifNonNull(
+        from.getCustomerEncryption(), this::customerEncryptionDecode, to::setCustomerEncryption);
+    ifNonNull(from.getStorageClass(), StorageClass::valueOf, to::setStorageClass);
+    ifNonNull(
+        from.getTimeStorageClassUpdated(), DateTime::getValue, to::setTimeStorageClassUpdated);
+    ifNonNull(from.getKmsKeyName(), to::setKmsKeyName);
+    ifNonNull(from.getEventBasedHold(), to::setEventBasedHold);
+    ifNonNull(from.getTemporaryHold(), to::setTemporaryHold);
+    ifNonNull(
+        from.getRetentionExpirationTime(), DateTime::getValue, to::setRetentionExpirationTime);
+    return to.build();
+  }
+
+  private StorageObject blobIdEncode(BlobId from) {
+    StorageObject to = new StorageObject();
+    to.setBucket(from.getBucket());
+    to.setName(from.getName());
+    to.setGeneration(from.getGeneration());
+    return to;
+  }
+
+  private BlobId blobIdDecode(StorageObject from) {
+    return BlobId.of(from.getBucket(), from.getName(), from.getGeneration());
+  }
+
+  private StorageObject.CustomerEncryption customerEncryptionEncode(CustomerEncryption from) {
+    return new StorageObject.CustomerEncryption()
+        .setEncryptionAlgorithm(from.getEncryptionAlgorithm())
+        .setKeySha256(from.getKeySha256());
+  }
+
+  private CustomerEncryption customerEncryptionDecode(StorageObject.CustomerEncryption from) {
+    return new CustomerEncryption(from.getEncryptionAlgorithm(), from.getKeySha256());
+  }
+
+  private Bucket bucketInfoEncode(BucketInfo from) {
+    Bucket to = new Bucket();
+    ifNonNull(from.getAcl(), toImmutableListOf(bucketAcl()::encode), to::setAcl);
+    ifNonNull(from.getCors(), toImmutableListOf(cors()::encode), to::setCors);
+    ifNonNull(from.getCreateTime(), DateTime::new, to::setTimeCreated);
+    ifNonNull(
+        from.getDefaultAcl(), toImmutableListOf(objectAcl()::encode), to::setDefaultObjectAcl);
+    ifNonNull(from.getLocation(), to::setLocation);
+    ifNonNull(from.getLocationType(), to::setLocationType);
+    ifNonNull(from.getMetageneration(), to::setMetageneration);
+    ifNonNull(
+        from.getOwner(),
+        lift(this::entityEncode).andThen(o -> new Bucket.Owner().setEntity(o)),
+        to::setOwner);
+    ifNonNull(from.getRpo(), Rpo::toString, to::setRpo);
+    ifNonNull(from.getStorageClass(), StorageClass::toString, to::setStorageClass);
+    ifNonNull(from.getUpdateTime(), DateTime::new, to::setUpdated);
+    ifNonNull(from.versioningEnabled(), b -> new Versioning().setEnabled(b), to::setVersioning);
+    to.setEtag(from.getEtag());
+    to.setId(from.getGeneratedId());
+    to.setName(from.getName());
+    to.setSelfLink(from.getSelfLink());
+
+    ifNonNull(from.requesterPays(), b -> new Bucket.Billing().setRequesterPays(b), to::setBilling);
+    if (from.getIndexPage() != null || from.getNotFoundPage() != null) {
+      Website website = new Website();
+      website.setMainPageSuffix(from.getIndexPage());
+      website.setNotFoundPage(from.getNotFoundPage());
+      to.setWebsite(website);
+    }
+
+    @SuppressWarnings("deprecation")
+    List<? extends DeleteRule> deleteRules = from.getDeleteRules();
+    // Do not use, #getLifecycleRules, it can not return null, which is important to our logic here
+    List<? extends LifecycleRule> lifecycleRules = from.lifecycleRules;
+    if (deleteRules != null || lifecycleRules != null) {
+      Lifecycle lifecycle = new Lifecycle();
+
+      // Here we determine if we need to "clear" any defined Lifecycle rules by explicitly setting
+      // the Rule list of lifecycle to the empty list.
+      // In order for us to clear the rules, one of the three following must be true:
+      //   1. deleteRules is null while lifecycleRules is non-null and empty
+      //   2. lifecycleRules is null while deleteRules is non-null and empty
+      //   3. lifecycleRules is non-null and empty while deleteRules is non-null and empty
+      // If none of the above three is true, we will interpret as the Lifecycle rules being
+      // updated to the defined set of DeleteRule and LifecycleRule.
+      if ((deleteRules == null && lifecycleRules.isEmpty())
+          || (lifecycleRules == null && deleteRules.isEmpty())
+          || (deleteRules != null && deleteRules.isEmpty() && lifecycleRules.isEmpty())) {
+        lifecycle.setRule(Collections.emptyList());
+      } else {
+        Set<Rule> rules = new HashSet<>();
+        ifNonNull(deleteRules, r -> r.stream().map(deleteRule()::encode).forEach(rules::add));
+        ifNonNull(lifecycleRules, r -> r.stream().map(lifecycleRule()::encode).forEach(rules::add));
+        if (!rules.isEmpty()) {
+          lifecycle.setRule(ImmutableList.copyOf(rules));
+        }
+      }
+
+      to.setLifecycle(lifecycle);
+    }
+
+    ifNonNull(from.getDefaultEventBasedHold(), to::setDefaultEventBasedHold);
+    ifNonNull(
+        from.getDefaultKmsKeyName(),
+        k -> new Encryption().setDefaultKmsKeyName(k),
+        to::setEncryption);
+    ifNonNull(from.getLabels(), to::setLabels);
+    if (from.getRetentionPeriod() != null) {
+      if (Data.isNull(from.getRetentionPeriod())) {
+        to.setRetentionPolicy(Data.nullOf(Bucket.RetentionPolicy.class));
+      } else {
+        Bucket.RetentionPolicy retentionPolicy = new Bucket.RetentionPolicy();
+        retentionPolicy.setRetentionPeriod(from.getRetentionPeriod());
+        if (from.getRetentionEffectiveTime() != null) {
+          retentionPolicy.setEffectiveTime(new DateTime(from.getRetentionEffectiveTime()));
+        }
+        if (from.retentionPolicyIsLocked() != null) {
+          retentionPolicy.setIsLocked(from.retentionPolicyIsLocked());
+        }
+        to.setRetentionPolicy(retentionPolicy);
+      }
+    }
+    ifNonNull(from.getIamConfiguration(), this::iamConfigEncode, to::setIamConfiguration);
+    ifNonNull(from.getLogging(), this::loggingEncode, to::setLogging);
+    return to;
+  }
+
+  @SuppressWarnings("deprecation")
+  private BucketInfo bucketInfoDecode(com.google.api.services.storage.model.Bucket from) {
+    BucketInfo.Builder to = new BuilderImpl(from.getName());
+    ifNonNull(from.getAcl(), toImmutableListOf(bucketAcl()::decode), to::setAcl);
+    ifNonNull(from.getCors(), toImmutableListOf(cors()::decode), to::setCors);
+    ifNonNull(
+        from.getDefaultObjectAcl(), toImmutableListOf(objectAcl()::decode), to::setDefaultAcl);
+    ifNonNull(from.getEtag(), to::setEtag);
+    ifNonNull(from.getId(), to::setGeneratedId);
+    ifNonNull(from.getLocation(), to::setLocation);
+    ifNonNull(from.getLocationType(), to::setLocationType);
+    ifNonNull(from.getMetageneration(), to::setMetageneration);
+    ifNonNull(
+        from.getOwner(), lift(Bucket.Owner::getEntity).andThen(this::entityDecode), to::setOwner);
+    ifNonNull(from.getRpo(), Rpo::valueOf, to::setRpo);
+    ifNonNull(from.getSelfLink(), to::setSelfLink);
+    ifNonNull(from.getStorageClass(), StorageClass::valueOf, to::setStorageClass);
+    ifNonNull(from.getTimeCreated(), DateTime::getValue, to::setCreateTime);
+    ifNonNull(from.getUpdated(), DateTime::getValue, to::setUpdateTime);
+    ifNonNull(from.getVersioning(), Versioning::getEnabled, to::setVersioningEnabled);
+    ifNonNull(from.getWebsite(), Website::getMainPageSuffix, to::setIndexPage);
+    ifNonNull(from.getWebsite(), Website::getNotFoundPage, to::setNotFoundPage);
+    ifNonNull(
+        from.getLifecycle(),
+        lift(Lifecycle::getRule).andThen(toImmutableListOf(lifecycleRule()::decode)),
+        to::setLifecycleRules);
+    // preserve mapping to deprecated property
+    ifNonNull(
+        from.getLifecycle(),
+        lift(Lifecycle::getRule).andThen(toImmutableListOf(deleteRule()::decode)),
+        to::setDeleteRules);
+    ifNonNull(from.getDefaultEventBasedHold(), to::setDefaultEventBasedHold);
+    ifNonNull(from.getLabels(), to::setLabels);
+    ifNonNull(from.getBilling(), Billing::getRequesterPays, to::setRequesterPays);
+    Encryption encryption = from.getEncryption();
+    if (encryption != null
+        && encryption.getDefaultKmsKeyName() != null
+        && !encryption.getDefaultKmsKeyName().isEmpty()) {
+      to.setDefaultKmsKeyName(encryption.getDefaultKmsKeyName());
+    }
+
+    RetentionPolicy retentionPolicy = from.getRetentionPolicy();
+    if (retentionPolicy != null && retentionPolicy.getEffectiveTime() != null) {
+      to.setRetentionEffectiveTime(retentionPolicy.getEffectiveTime().getValue());
+    }
+    ifNonNull(retentionPolicy, RetentionPolicy::getIsLocked, to::setRetentionPolicyIsLocked);
+    ifNonNull(retentionPolicy, RetentionPolicy::getRetentionPeriod, to::setRetentionPeriod);
+    ifNonNull(from.getIamConfiguration(), this::iamConfigDecode, to::setIamConfiguration);
+    ifNonNull(from.getLogging(), this::loggingDecode, to::setLogging);
+
+    return to.build();
+  }
+
+  @SuppressWarnings("deprecation")
+  private Rule deleteRuleEncode(DeleteRule from) {
+    if (from instanceof RawDeleteRule) {
+      RawDeleteRule rule = (RawDeleteRule) from;
+      return rule.getRule();
+    }
+    Rule to = new Rule();
+    to.setAction(new Rule.Action().setType(DeleteRule.SUPPORTED_ACTION));
+    Rule.Condition condition = new Rule.Condition();
+    from.populateCondition(condition);
+    to.setCondition(condition);
+    return to;
+  }
+
+  @SuppressWarnings("deprecation")
+  private DeleteRule deleteRuleDecode(Rule from) { // TODO: Name/type cleanup
+    if (from.getAction() != null
+        && DeleteRule.SUPPORTED_ACTION.endsWith(from.getAction().getType())) {
+      Rule.Condition condition = from.getCondition();
+      Integer age = condition.getAge();
+      if (age != null) {
+        return new AgeDeleteRule(age);
+      }
+      DateTime dateTime = condition.getCreatedBefore();
+      if (dateTime != null) {
+        return new CreatedBeforeDeleteRule(dateTime.getValue());
+      }
+      Integer numNewerVersions = condition.getNumNewerVersions();
+      if (numNewerVersions != null) {
+        return new NumNewerVersionsDeleteRule(numNewerVersions);
+      }
+      Boolean isLive = condition.getIsLive();
+      if (isLive != null) {
+        return new IsLiveDeleteRule(isLive);
+      }
+    }
+    return new RawDeleteRule(from);
+  }
+
+  private Bucket.IamConfiguration iamConfigEncode(IamConfiguration from) {
+    Bucket.IamConfiguration to = new Bucket.IamConfiguration();
+    to.setUniformBucketLevelAccess(ublaEncode(from));
+    ifNonNull(
+        from.getPublicAccessPrevention(),
+        PublicAccessPrevention::getValue,
+        to::setPublicAccessPrevention);
+    return to;
+  }
+
+  private IamConfiguration iamConfigDecode(Bucket.IamConfiguration from) {
+    Bucket.IamConfiguration.UniformBucketLevelAccess ubla = from.getUniformBucketLevelAccess();
+
+    IamConfiguration.Builder to =
+        IamConfiguration.newBuilder().setIsUniformBucketLevelAccessEnabled(ubla.getEnabled());
+    ifNonNull(ubla.getLockedTime(), DateTime::getValue, to::setUniformBucketLevelAccessLockedTime);
+    ifNonNull(
+        from.getPublicAccessPrevention(),
+        PublicAccessPrevention::parse,
+        to::setPublicAccessPrevention);
+    return to.build();
+  }
+
+  private UniformBucketLevelAccess ublaEncode(IamConfiguration from) {
+    UniformBucketLevelAccess to = new UniformBucketLevelAccess();
+    to.setEnabled(from.isUniformBucketLevelAccessEnabled());
+    ifNonNull(from.getUniformBucketLevelAccessLockedTime(), DateTime::new, to::setLockedTime);
+    return to;
+  }
+
+  private Rule lifecycleRuleEncode(LifecycleRule from) {
+    Rule to = new Rule();
+    to.setAction(ruleActionEncode(from.getLifecycleAction()));
+    to.setCondition(ruleConditionEncode(from.getLifecycleCondition()));
+    return to;
+  }
+
+  private Condition ruleConditionEncode(LifecycleCondition from) {
+    Condition to =
+        new Condition()
+            .setAge(from.getAge())
+            .setIsLive(from.getIsLive())
+            .setNumNewerVersions(from.getNumberOfNewerVersions())
+            .setDaysSinceNoncurrentTime(from.getDaysSinceNoncurrentTime())
+            .setDaysSinceCustomTime(from.getDaysSinceCustomTime());
+    ifNonNull(from.getCreatedBefore(), this::truncateToDateWithNoTzDrift, to::setCreatedBefore);
+    ifNonNull(
+        from.getNoncurrentTimeBefore(),
+        this::truncateToDateWithNoTzDrift,
+        to::setNoncurrentTimeBefore);
+    ifNonNull(
+        from.getCustomTimeBefore(), this::truncateToDateWithNoTzDrift, to::setCustomTimeBefore);
+    ifNonNull(
+        from.getMatchesStorageClass(),
+        toImmutableListOf(Object::toString),
+        to::setMatchesStorageClass);
+    return to;
+  }
+
+  private Action ruleActionEncode(LifecycleAction from) {
+    Action to = new Action().setType(from.getActionType());
+    if (from.getActionType().equals(SetStorageClassLifecycleAction.TYPE)) {
+      to.setStorageClass(((SetStorageClassLifecycleAction) from).getStorageClass().toString());
+    }
+    return to;
+  }
+
+  private LifecycleRule lifecycleRuleDecode(Rule from) {
+    LifecycleAction lifecycleAction;
+
+    Rule.Action action = from.getAction();
+
+    switch (action.getType()) {
+      case DeleteLifecycleAction.TYPE:
+        lifecycleAction = LifecycleAction.newDeleteAction();
+        break;
+      case SetStorageClassLifecycleAction.TYPE:
+        lifecycleAction =
+            LifecycleAction.newSetStorageClassAction(
+                StorageClass.valueOf(action.getStorageClass()));
+        break;
+      default:
+        BucketInfo.log.warning(
+            "The lifecycle action "
+                + action.getType()
+                + " is not supported by this version of the library. "
+                + "Attempting to update with this rule may cause errors. Please "
+                + "update to the latest version of google-cloud-storage.");
+        lifecycleAction = LifecycleAction.newLifecycleAction("Unknown action");
+    }
+
+    Rule.Condition condition = from.getCondition();
+
+    LifecycleCondition.Builder conditionBuilder =
+        LifecycleCondition.newBuilder()
+            .setAge(condition.getAge())
+            .setCreatedBefore(condition.getCreatedBefore())
+            .setIsLive(condition.getIsLive())
+            .setNumberOfNewerVersions(condition.getNumNewerVersions())
+            .setDaysSinceNoncurrentTime(condition.getDaysSinceNoncurrentTime())
+            .setNoncurrentTimeBefore(condition.getNoncurrentTimeBefore())
+            .setCustomTimeBefore(condition.getCustomTimeBefore())
+            .setDaysSinceCustomTime(condition.getDaysSinceCustomTime());
+    ifNonNull(
+        condition.getMatchesStorageClass(),
+        toImmutableListOf(StorageClass::valueOf),
+        conditionBuilder::setMatchesStorageClass);
+
+    return new LifecycleRule(lifecycleAction, conditionBuilder.build());
+  }
+
+  private Bucket.Logging loggingEncode(Logging from) {
+    Bucket.Logging to;
+    if (from.getLogBucket() != null || from.getLogObjectPrefix() != null) {
+      to = new Bucket.Logging();
+      to.setLogBucket(from.getLogBucket());
+      to.setLogObjectPrefix(from.getLogObjectPrefix());
+    } else {
+      to = Data.nullOf(Bucket.Logging.class);
+    }
+    return to;
+  }
+
+  private Logging loggingDecode(Bucket.Logging from) {
+    return Logging.newBuilder()
+        .setLogBucket(from.getLogBucket())
+        .setLogObjectPrefix(from.getLogObjectPrefix())
+        .build();
+  }
+
+  private Bucket.Cors corsEncode(Cors from) {
+    Bucket.Cors to = new Bucket.Cors();
+    to.setMaxAgeSeconds(from.getMaxAgeSeconds());
+    to.setResponseHeader(from.getResponseHeaders());
+    ifNonNull(from.getMethods(), toImmutableListOf(Object::toString), to::setMethod);
+    ifNonNull(from.getOrigins(), toImmutableListOf(Object::toString), to::setOrigin);
+    return to;
+  }
+
+  private Cors corsDecode(Bucket.Cors from) {
+    Cors.Builder to = Cors.newBuilder().setMaxAgeSeconds(from.getMaxAgeSeconds());
+    ifNonNull(
+        from.getMethod(),
+        m ->
+            m.stream()
+                .map(String::toUpperCase)
+                .map(HttpMethod::valueOf)
+                .collect(ImmutableList.toImmutableList()),
+        to::setMethods);
+    ifNonNull(from.getOrigin(), toImmutableListOf(Origin::of), to::setOrigins);
+    to.setResponseHeaders(from.getResponseHeader());
+    return to.build();
+  }
+
+  private com.google.api.services.storage.model.ServiceAccount serviceAccountEncode(
+      ServiceAccount from) {
+    return new com.google.api.services.storage.model.ServiceAccount()
+        .setEmailAddress(from.getEmail());
+  }
+
+  private ServiceAccount serviceAccountDecode(
+      com.google.api.services.storage.model.ServiceAccount from) {
+    return ServiceAccount.of(from.getEmailAddress());
+  }
+
+  private com.google.api.services.storage.model.HmacKey hmacKeyEncode(HmacKey from) {
+    com.google.api.services.storage.model.HmacKey to =
+        new com.google.api.services.storage.model.HmacKey();
+    to.setSecret(from.getSecretKey());
+    ifNonNull(from.getMetadata(), this::hmacKeyMetadataEncode, to::setMetadata);
+    return to;
+  }
+
+  private HmacKey hmacKeyDecode(com.google.api.services.storage.model.HmacKey from) {
+    return HmacKey.newBuilder(from.getSecret())
+        .setMetadata(hmacKeyMetadataDecode(from.getMetadata()))
+        .build();
+  }
+
+  private com.google.api.services.storage.model.HmacKeyMetadata hmacKeyMetadataEncode(
+      HmacKeyMetadata from) {
+    com.google.api.services.storage.model.HmacKeyMetadata to =
+        new com.google.api.services.storage.model.HmacKeyMetadata();
+    to.setAccessId(from.getAccessId());
+    to.setEtag(from.getEtag());
+    to.setId(from.getId());
+    to.setProjectId(from.getProjectId());
+    ifNonNull(from.getServiceAccount(), ServiceAccount::getEmail, to::setServiceAccountEmail);
+    ifNonNull(from.getState(), Object::toString, to::setState);
+    ifNonNull(from.getCreateTime(), DateTime::new, to::setTimeCreated);
+    ifNonNull(from.getUpdateTime(), DateTime::new, to::setUpdated);
+    return to;
+  }
+
+  private HmacKeyMetadata hmacKeyMetadataDecode(
+      com.google.api.services.storage.model.HmacKeyMetadata from) {
+    return HmacKeyMetadata.newBuilder(ServiceAccount.of(from.getServiceAccountEmail()))
+        .setAccessId(from.getAccessId())
+        .setCreateTime(from.getTimeCreated().getValue())
+        .setEtag(from.getEtag())
+        .setId(from.getId())
+        .setProjectId(from.getProjectId())
+        .setState(HmacKeyState.valueOf(from.getState()))
+        .setUpdateTime(from.getUpdated().getValue())
+        .build();
+  }
+
+  private String entityEncode(Entity from) {
+    if (from instanceof RawEntity) {
+      return from.getValue();
+    } else if (from instanceof User) {
+      switch (from.getValue()) {
+        case User.ALL_AUTHENTICATED_USERS:
+          return User.ALL_AUTHENTICATED_USERS;
+        case User.ALL_USERS:
+          return User.ALL_USERS;
+        default:
+          break;
+      }
+    }
+
+    // intentionally not an else so that if the default is hit above it will fall through to here
+    return from.getType().name().toLowerCase() + "-" + from.getValue();
+  }
+
+  private Entity entityDecode(String from) {
+    if (from.startsWith("user-")) {
+      return new User(from.substring(5));
+    }
+    if (from.equals(User.ALL_USERS)) {
+      return User.ofAllUsers();
+    }
+    if (from.equals(User.ALL_AUTHENTICATED_USERS)) {
+      return User.ofAllAuthenticatedUsers();
+    }
+    if (from.startsWith("group-")) {
+      return new Group(from.substring(6));
+    }
+    if (from.startsWith("domain-")) {
+      return new Domain(from.substring(7));
+    }
+    if (from.startsWith("project-")) {
+      int idx = from.indexOf('-', 8);
+      String team = from.substring(8, idx);
+      String projectId = from.substring(idx + 1);
+      return new Project(Project.ProjectRole.valueOf(team.toUpperCase()), projectId);
+    }
+    return new RawEntity(from);
+  }
+
+  private Acl objectAclDecode(ObjectAccessControl from) {
+    Role role = Role.valueOf(from.getRole());
+    Entity entity = entityDecode(from.getEntity());
+    return Acl.newBuilder(entity, role).setEtag(from.getEtag()).setId(from.getId()).build();
+  }
+
+  private Acl bucketAclDecode(BucketAccessControl from) {
+    Role role = Role.valueOf(from.getRole());
+    Entity entity = entityDecode(from.getEntity());
+    return Acl.newBuilder(entity, role).setEtag(from.getEtag()).setId(from.getId()).build();
+  }
+
+  private BucketAccessControl bucketAclEncode(Acl from) {
+    return new BucketAccessControl()
+        .setEntity(from.getEntity().toString())
+        .setRole(from.getRole().toString())
+        .setId(from.getId())
+        .setEtag(from.getEtag());
+  }
+
+  private ObjectAccessControl objectAclEncode(Acl from) {
+    return new ObjectAccessControl()
+        .setEntity(entityEncode(from.getEntity()))
+        .setRole(from.getRole().name())
+        .setId(from.getId())
+        .setEtag(from.getEtag());
+  }
+
+  private com.google.api.services.storage.model.Notification notificationEncode(
+      NotificationInfo from) {
+    com.google.api.services.storage.model.Notification to =
+        new com.google.api.services.storage.model.Notification();
+
+    to.setEtag(from.getEtag());
+    to.setSelfLink(from.getSelfLink());
+    to.setTopic(from.getTopic());
+    ifNonNull(from.getNotificationId(), to::setId);
+    ifNonNull(from.getCustomAttributes(), to::setCustomAttributes);
+    ifNonNull(from.getObjectNamePrefix(), to::setObjectNamePrefix);
+
+    List<EventType> eventTypes = from.getEventTypes();
+    if (eventTypes != null && eventTypes.size() > 0) {
+      List<String> eventTypesPb = new ArrayList<>();
+      for (EventType eventType : eventTypes) {
+        eventTypesPb.add(eventType.toString());
+      }
+      to.setEventTypes(eventTypesPb);
+    }
+
+    PayloadFormat payloadFormat = from.getPayloadFormat();
+    if (payloadFormat != null) {
+      to.setPayloadFormat(payloadFormat.toString());
+    } else {
+      to.setPayloadFormat(PayloadFormat.NONE.toString());
+    }
+    return to;
+  }
+
+  private NotificationInfo notificationDecode(
+      com.google.api.services.storage.model.Notification from) {
+    NotificationInfo.Builder builder = new NotificationInfo.BuilderImpl(from.getTopic());
+    ifNonNull(from.getId(), builder::setNotificationId);
+    ifNonNull(from.getEtag(), builder::setEtag);
+    ifNonNull(from.getCustomAttributes(), builder::setCustomAttributes);
+    ifNonNull(from.getSelfLink(), builder::setSelfLink);
+    ifNonNull(from.getObjectNamePrefix(), builder::setObjectNamePrefix);
+    ifNonNull(from.getPayloadFormat(), PayloadFormat::valueOf, builder::setPayloadFormat);
+
+    if (from.getEventTypes() != null) {
+      List<String> eventTypesPb = from.getEventTypes();
+      EventType[] eventTypes = new EventType[eventTypesPb.size()];
+      for (int index = 0; index < eventTypesPb.size(); index++) {
+        eventTypes[index] = EventType.valueOf(eventTypesPb.get(index));
+      }
+      builder.setEventTypes(eventTypes);
+    }
+    return builder.build();
+  }
+
+  private DateTime truncateToDateWithNoTzDrift(DateTime dt) {
+    return new DateTime(true, dt.getValue(), 0);
+  }
+
+  /**
+   * Several properties are translating lists of one type to another. This convenience method allows
+   * specifying a mapping function and composing as part of an {@code #isNonNull} definition.
+   */
+  private static <T1, T2> Function<List<T1>, ImmutableList<T2>> toImmutableListOf(
+      Function<T1, T2> f) {
+    return l -> l.stream().map(f).collect(ImmutableList.toImmutableList());
+  }
+}
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
index 79167aec..a812e585 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
@@ -20,7 +20,6 @@ import static com.google.cloud.storage.Blob.BlobSourceOption.toGetOptions;
 import static com.google.cloud.storage.Blob.BlobSourceOption.toSourceOptions;
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import com.google.api.services.storage.model.StorageObject;
 import com.google.auth.ServiceAccountSigner;
 import com.google.auth.ServiceAccountSigner.SigningException;
 import com.google.cloud.ReadChannel;
@@ -915,7 +914,10 @@ public class Blob extends BlobInfo {
       return false;
     }
     Blob other = (Blob) obj;
-    return Objects.equals(toPb(), other.toPb()) && Objects.equals(options, other.options);
+    return Objects.equals(
+            Conversions.apiary().blobInfo().encode(this),
+            Conversions.apiary().blobInfo().encode(other))
+        && Objects.equals(options, other.options);
   }
 
   @Override
@@ -927,9 +929,4 @@ public class Blob extends BlobInfo {
     in.defaultReadObject();
     this.storage = options.getService();
   }
-
-  static Blob fromPb(Storage storage, StorageObject storageObject) {
-    BlobInfo info = BlobInfo.fromPb(storageObject);
-    return new Blob(storage, new BlobInfo.BuilderImpl(info));
-  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobId.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobId.java
index 599f45e2..4cf73699 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobId.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobId.java
@@ -18,7 +18,6 @@ package com.google.cloud.storage;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import com.google.api.services.storage.model.StorageObject;
 import com.google.common.base.MoreObjects;
 import java.io.Serializable;
 import java.util.Objects;
@@ -90,14 +89,6 @@ public final class BlobId implements Serializable {
         && Objects.equals(generation, other.generation);
   }
 
-  StorageObject toPb() {
-    StorageObject storageObject = new StorageObject();
-    storageObject.setBucket(bucket);
-    storageObject.setName(name);
-    storageObject.setGeneration(generation);
-    return storageObject;
-  }
-
   /**
    * Creates a blob identifier. Generation is set to {@code null}.
    *
@@ -136,9 +127,4 @@ public final class BlobId implements Serializable {
 
     return BlobId.of(bucketName, blobName);
   }
-
-  static BlobId fromPb(StorageObject storageObject) {
-    return BlobId.of(
-        storageObject.getBucket(), storageObject.getName(), storageObject.getGeneration());
-  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java
index 0604a117..f5e4550e 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java
@@ -20,20 +20,12 @@ import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import com.google.api.client.util.Data;
-import com.google.api.client.util.DateTime;
 import com.google.api.core.BetaApi;
-import com.google.api.services.storage.model.ObjectAccessControl;
-import com.google.api.services.storage.model.StorageObject;
-import com.google.api.services.storage.model.StorageObject.Owner;
-import com.google.common.base.Function;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.common.io.BaseEncoding;
 import java.io.Serializable;
-import java.math.BigInteger;
 import java.nio.ByteBuffer;
 import java.util.AbstractMap;
 import java.util.Collections;
@@ -65,13 +57,6 @@ import java.util.Set;
  */
 public class BlobInfo implements Serializable {
 
-  static final Function<BlobInfo, StorageObject> INFO_TO_PB_FUNCTION =
-      new Function<BlobInfo, StorageObject>() {
-        @Override
-        public StorageObject apply(BlobInfo blobInfo) {
-          return blobInfo.toPb();
-        }
-      };
   private static final long serialVersionUID = -5625857076205028976L;
   private final BlobId blobId;
   private final String generatedId;
@@ -85,7 +70,14 @@ public class BlobInfo implements Serializable {
   private final String crc32c;
   private final Long customTime;
   private final String mediaLink;
-  private final Map<String, String> metadata;
+  /**
+   * The getter for this property never returns null, however null awareness is critical for
+   * encoding
+   *
+   * @see ApiaryConversions#blobInfo() encoder
+   */
+  final Map<String, String> metadata;
+
   private final Long metageneration;
   private final Long deleteTime;
   private final Long updateTime;
@@ -153,22 +145,16 @@ public class BlobInfo implements Serializable {
     }
 
     @Override
-    public final boolean equals(Object obj) {
-      return obj == this
-          || obj != null
-              && obj.getClass().equals(CustomerEncryption.class)
-              && Objects.equals(toPb(), ((CustomerEncryption) obj).toPb());
-    }
-
-    StorageObject.CustomerEncryption toPb() {
-      return new StorageObject.CustomerEncryption()
-          .setEncryptionAlgorithm(encryptionAlgorithm)
-          .setKeySha256(keySha256);
-    }
-
-    static CustomerEncryption fromPb(StorageObject.CustomerEncryption customerEncryptionPb) {
-      return new CustomerEncryption(
-          customerEncryptionPb.getEncryptionAlgorithm(), customerEncryptionPb.getKeySha256());
+    public final boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (!(o instanceof CustomerEncryption)) {
+        return false;
+      }
+      CustomerEncryption that = (CustomerEncryption) o;
+      return Objects.equals(encryptionAlgorithm, that.encryptionAlgorithm)
+          && Objects.equals(keySha256, that.keySha256);
     }
   }
 
@@ -440,25 +426,25 @@ public class BlobInfo implements Serializable {
 
     @Override
     public Builder setContentType(String contentType) {
-      this.contentType = firstNonNull(contentType, Data.<String>nullOf(String.class));
+      this.contentType = firstNonNull(contentType, Data.nullOf(String.class));
       return this;
     }
 
     @Override
     public Builder setContentDisposition(String contentDisposition) {
-      this.contentDisposition = firstNonNull(contentDisposition, Data.<String>nullOf(String.class));
+      this.contentDisposition = firstNonNull(contentDisposition, Data.nullOf(String.class));
       return this;
     }
 
     @Override
     public Builder setContentLanguage(String contentLanguage) {
-      this.contentLanguage = firstNonNull(contentLanguage, Data.<String>nullOf(String.class));
+      this.contentLanguage = firstNonNull(contentLanguage, Data.nullOf(String.class));
       return this;
     }
 
     @Override
     public Builder setContentEncoding(String contentEncoding) {
-      this.contentEncoding = firstNonNull(contentEncoding, Data.<String>nullOf(String.class));
+      this.contentEncoding = firstNonNull(contentEncoding, Data.nullOf(String.class));
       return this;
     }
 
@@ -470,7 +456,7 @@ public class BlobInfo implements Serializable {
 
     @Override
     public Builder setCacheControl(String cacheControl) {
-      this.cacheControl = firstNonNull(cacheControl, Data.<String>nullOf(String.class));
+      this.cacheControl = firstNonNull(cacheControl, Data.nullOf(String.class));
       return this;
     }
 
@@ -506,7 +492,7 @@ public class BlobInfo implements Serializable {
 
     @Override
     public Builder setMd5(String md5) {
-      this.md5 = firstNonNull(md5, Data.<String>nullOf(String.class));
+      this.md5 = firstNonNull(md5, Data.nullOf(String.class));
       return this;
     }
 
@@ -536,7 +522,7 @@ public class BlobInfo implements Serializable {
 
     @Override
     public Builder setCrc32c(String crc32c) {
-      this.crc32c = firstNonNull(crc32c, Data.<String>nullOf(String.class));
+      this.crc32c = firstNonNull(crc32c, Data.nullOf(String.class));
       return this;
     }
 
@@ -990,7 +976,7 @@ public class BlobInfo implements Serializable {
    */
   @BetaApi
   public Boolean getEventBasedHold() {
-    return Data.<Boolean>isNull(eventBasedHold) ? null : eventBasedHold;
+    return Data.isNull(eventBasedHold) ? null : eventBasedHold;
   }
 
   /**
@@ -1017,7 +1003,7 @@ public class BlobInfo implements Serializable {
    */
   @BetaApi
   public Boolean getTemporaryHold() {
-    return Data.<Boolean>isNull(temporaryHold) ? null : temporaryHold;
+    return Data.isNull(temporaryHold) ? null : temporaryHold;
   }
 
   /**
@@ -1026,7 +1012,7 @@ public class BlobInfo implements Serializable {
    */
   @BetaApi
   public Long getRetentionExpirationTime() {
-    return Data.<Long>isNull(retentionExpirationTime) ? null : retentionExpirationTime;
+    return Data.isNull(retentionExpirationTime) ? null : retentionExpirationTime;
   }
 
   /** Returns a builder for the current blob. */
@@ -1056,79 +1042,19 @@ public class BlobInfo implements Serializable {
     return obj == this
         || obj != null
             && obj.getClass().equals(BlobInfo.class)
-            && Objects.equals(toPb(), ((BlobInfo) obj).toPb());
+            && Objects.equals(
+                Conversions.apiary().blobInfo().encode(this),
+                Conversions.apiary()
+                    .blobInfo()
+                    .encode(((BlobInfo) obj))); // TODO: remove this excessive allocation
   }
 
-  StorageObject toPb() {
-    StorageObject storageObject = blobId.toPb();
-    if (acl != null) {
-      storageObject.setAcl(
-          Lists.transform(
-              acl,
-              new Function<Acl, ObjectAccessControl>() {
-                @Override
-                public ObjectAccessControl apply(Acl acl) {
-                  return acl.toObjectPb();
-                }
-              }));
-    }
-    if (deleteTime != null) {
-      storageObject.setTimeDeleted(new DateTime(deleteTime));
-    }
-    if (updateTime != null) {
-      storageObject.setUpdated(new DateTime(updateTime));
-    }
-    if (createTime != null) {
-      storageObject.setTimeCreated(new DateTime(createTime));
-    }
-    if (customTime != null) {
-      storageObject.setCustomTime(new DateTime(customTime));
-    }
-    if (size != null) {
-      storageObject.setSize(BigInteger.valueOf(size));
-    }
-    if (owner != null) {
-      storageObject.setOwner(new Owner().setEntity(owner.toPb()));
-    }
-    if (storageClass != null) {
-      storageObject.setStorageClass(storageClass.toString());
-    }
-    if (timeStorageClassUpdated != null) {
-      storageObject.setTimeStorageClassUpdated(new DateTime(timeStorageClassUpdated));
-    }
-
-    Map<String, String> pbMetadata = metadata;
-    if (metadata != null && !Data.isNull(metadata)) {
-      pbMetadata = Maps.newHashMapWithExpectedSize(metadata.size());
-      for (Map.Entry<String, String> entry : metadata.entrySet()) {
-        pbMetadata.put(
-            entry.getKey(), firstNonNull(entry.getValue(), Data.<String>nullOf(String.class)));
-      }
-    }
-    if (customerEncryption != null) {
-      storageObject.setCustomerEncryption(customerEncryption.toPb());
-    }
-    if (retentionExpirationTime != null) {
-      storageObject.setRetentionExpirationTime(new DateTime(retentionExpirationTime));
-    }
-    storageObject.setKmsKeyName(kmsKeyName);
-    storageObject.setEventBasedHold(eventBasedHold);
-    storageObject.setTemporaryHold(temporaryHold);
-    storageObject.setMetadata(pbMetadata);
-    storageObject.setCacheControl(cacheControl);
-    storageObject.setContentEncoding(contentEncoding);
-    storageObject.setCrc32c(crc32c);
-    storageObject.setContentType(contentType);
-    storageObject.setMd5Hash(md5);
-    storageObject.setMediaLink(mediaLink);
-    storageObject.setMetageneration(metageneration);
-    storageObject.setContentDisposition(contentDisposition);
-    storageObject.setComponentCount(componentCount);
-    storageObject.setContentLanguage(contentLanguage);
-    storageObject.setEtag(etag);
-    storageObject.setId(generatedId);
-    storageObject.setSelfLink(selfLink);
-    return storageObject;
+  /**
+   * Attach this instance to an instance of {@link Storage} thereby allowing RPCs to be performed
+   * using the methods from the resulting {@link Blob}
+   */
+  Blob asBlob(Storage storage) {
+    return new Blob(storage, new BuilderImpl(this));
   }
 
   /** Returns a {@code BlobInfo} builder where blob identity is set using the provided values. */
@@ -1155,105 +1081,4 @@ public class BlobInfo implements Serializable {
   public static Builder newBuilder(BlobId blobId) {
     return new BuilderImpl(blobId);
   }
-
-  static BlobInfo fromPb(StorageObject storageObject) {
-    Builder builder = newBuilder(BlobId.fromPb(storageObject));
-    if (storageObject.getCacheControl() != null) {
-      builder.setCacheControl(storageObject.getCacheControl());
-    }
-    if (storageObject.getContentEncoding() != null) {
-      builder.setContentEncoding(storageObject.getContentEncoding());
-    }
-    if (storageObject.getCrc32c() != null) {
-      builder.setCrc32c(storageObject.getCrc32c());
-    }
-    if (storageObject.getContentType() != null) {
-      builder.setContentType(storageObject.getContentType());
-    }
-    if (storageObject.getMd5Hash() != null) {
-      builder.setMd5(storageObject.getMd5Hash());
-    }
-    if (storageObject.getMediaLink() != null) {
-      builder.setMediaLink(storageObject.getMediaLink());
-    }
-    if (storageObject.getMetageneration() != null) {
-      builder.setMetageneration(storageObject.getMetageneration());
-    }
-    if (storageObject.getContentDisposition() != null) {
-      builder.setContentDisposition(storageObject.getContentDisposition());
-    }
-    if (storageObject.getComponentCount() != null) {
-      builder.setComponentCount(storageObject.getComponentCount());
-    }
-    if (storageObject.getContentLanguage() != null) {
-      builder.setContentLanguage(storageObject.getContentLanguage());
-    }
-    if (storageObject.getEtag() != null) {
-      builder.setEtag(storageObject.getEtag());
-    }
-    if (storageObject.getId() != null) {
-      builder.setGeneratedId(storageObject.getId());
-    }
-    if (storageObject.getSelfLink() != null) {
-      builder.setSelfLink(storageObject.getSelfLink());
-    }
-    if (storageObject.getMetadata() != null) {
-      builder.setMetadata(storageObject.getMetadata());
-    }
-    if (storageObject.getTimeDeleted() != null) {
-      builder.setDeleteTime(storageObject.getTimeDeleted().getValue());
-    }
-    if (storageObject.getUpdated() != null) {
-      builder.setUpdateTime(storageObject.getUpdated().getValue());
-    }
-    if (storageObject.getTimeCreated() != null) {
-      builder.setCreateTime(storageObject.getTimeCreated().getValue());
-    }
-    if (storageObject.getCustomTime() != null) {
-      builder.setCustomTime(storageObject.getCustomTime().getValue());
-    }
-    if (storageObject.getSize() != null) {
-      builder.setSize(storageObject.getSize().longValue());
-    }
-    if (storageObject.getOwner() != null) {
-      builder.setOwner(Acl.Entity.fromPb(storageObject.getOwner().getEntity()));
-    }
-    if (storageObject.getAcl() != null) {
-      builder.setAcl(
-          Lists.transform(
-              storageObject.getAcl(),
-              new Function<ObjectAccessControl, Acl>() {
-                @Override
-                public Acl apply(ObjectAccessControl objectAccessControl) {
-                  return Acl.fromPb(objectAccessControl);
-                }
-              }));
-    }
-    if (storageObject.containsKey("isDirectory")) {
-      builder.setIsDirectory(Boolean.TRUE);
-    }
-    if (storageObject.getCustomerEncryption() != null) {
-      builder.setCustomerEncryption(
-          CustomerEncryption.fromPb(storageObject.getCustomerEncryption()));
-    }
-    if (storageObject.getStorageClass() != null) {
-      builder.setStorageClass(StorageClass.valueOf(storageObject.getStorageClass()));
-    }
-    if (storageObject.getTimeStorageClassUpdated() != null) {
-      builder.setTimeStorageClassUpdated(storageObject.getTimeStorageClassUpdated().getValue());
-    }
-    if (storageObject.getKmsKeyName() != null) {
-      builder.setKmsKeyName(storageObject.getKmsKeyName());
-    }
-    if (storageObject.getEventBasedHold() != null) {
-      builder.setEventBasedHold(storageObject.getEventBasedHold());
-    }
-    if (storageObject.getTemporaryHold() != null) {
-      builder.setTemporaryHold(storageObject.getTemporaryHold());
-    }
-    if (storageObject.getRetentionExpirationTime() != null) {
-      builder.setRetentionExpirationTime(storageObject.getRetentionExpirationTime().getValue());
-    }
-    return builder.build();
-  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannel.java
index a477bc9d..f89c8efc 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannel.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannel.java
@@ -63,7 +63,7 @@ class BlobReadChannel implements ReadChannel {
     this.retryAlgorithmManager = serviceOptions.getRetryAlgorithmManager();
     isOpen = true;
     storageRpc = serviceOptions.getStorageRpcV1();
-    storageObject = blob.toPb();
+    storageObject = Conversions.apiary().blobId().encode(blob);
     this.limit = Long.MAX_VALUE;
   }
 
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
index 95acd19d..28eab484 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
@@ -1233,7 +1233,10 @@ public class Bucket extends BucketInfo {
       return false;
     }
     Bucket other = (Bucket) obj;
-    return Objects.equals(toPb(), other.toPb()) && Objects.equals(options, other.options);
+    return Objects.equals(
+            Conversions.apiary().bucketInfo().encode(this),
+            Conversions.apiary().bucketInfo().encode(other))
+        && Objects.equals(options, other.options);
   }
 
   @Override
@@ -1245,8 +1248,4 @@ public class Bucket extends BucketInfo {
     in.defaultReadObject();
     this.storage = options.getService();
   }
-
-  static Bucket fromPb(Storage storage, com.google.api.services.storage.model.Bucket bucketPb) {
-    return new Bucket(storage, new BucketInfo.BuilderImpl(BucketInfo.fromPb(bucketPb)));
-  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
index e03110ff..20813c44 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
@@ -18,24 +18,14 @@ package com.google.cloud.storage;
 
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Lists.transform;
 
 import com.google.api.client.json.jackson2.JacksonFactory;
 import com.google.api.client.util.Data;
 import com.google.api.client.util.DateTime;
 import com.google.api.core.BetaApi;
-import com.google.api.services.storage.model.Bucket;
-import com.google.api.services.storage.model.Bucket.Encryption;
-import com.google.api.services.storage.model.Bucket.Lifecycle;
 import com.google.api.services.storage.model.Bucket.Lifecycle.Rule;
-import com.google.api.services.storage.model.Bucket.Owner;
-import com.google.api.services.storage.model.Bucket.Versioning;
-import com.google.api.services.storage.model.Bucket.Website;
-import com.google.api.services.storage.model.BucketAccessControl;
-import com.google.api.services.storage.model.ObjectAccessControl;
 import com.google.cloud.storage.Acl.Entity;
 import com.google.common.base.Function;
-import com.google.common.base.Functions;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
@@ -43,12 +33,9 @@ import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
-import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.logging.Logger;
 
 /**
@@ -59,20 +46,6 @@ import java.util.logging.Logger;
  */
 public class BucketInfo implements Serializable {
 
-  static final Function<com.google.api.services.storage.model.Bucket, BucketInfo> FROM_PB_FUNCTION =
-      new Function<com.google.api.services.storage.model.Bucket, BucketInfo>() {
-        @Override
-        public BucketInfo apply(com.google.api.services.storage.model.Bucket pb) {
-          return BucketInfo.fromPb(pb);
-        }
-      };
-  static final Function<BucketInfo, com.google.api.services.storage.model.Bucket> TO_PB_FUNCTION =
-      new Function<BucketInfo, com.google.api.services.storage.model.Bucket>() {
-        @Override
-        public com.google.api.services.storage.model.Bucket apply(BucketInfo bucketInfo) {
-          return bucketInfo.toPb();
-        }
-      };
   private static final long serialVersionUID = -4712013629621638459L;
   private final String generatedId;
   private final String name;
@@ -83,7 +56,14 @@ public class BucketInfo implements Serializable {
   private final String indexPage;
   private final String notFoundPage;
   private final List<DeleteRule> deleteRules;
-  private final List<LifecycleRule> lifecycleRules;
+  /**
+   * The getter for this property never returns null, however null awareness is critical for
+   * encoding to properly determine how to process rules conversion.
+   *
+   * @see ApiaryConversions#bucketInfo() encoder
+   */
+  final List<LifecycleRule> lifecycleRules;
+
   private final String etag;
   private final Long createTime;
   private final Long updateTime;
@@ -104,7 +84,12 @@ public class BucketInfo implements Serializable {
   private final String locationType;
   private final Logging logging;
 
-  private static final Logger log = Logger.getLogger(BucketInfo.class.getName());
+  /**
+   * non-private for backward compatibility on message class. log messages are now emitted from
+   *
+   * @see ApiaryConversions#lifecycleRule()
+   */
+  static final Logger log = Logger.getLogger(BucketInfo.class.getName());
 
   /**
    * Public Access Prevention enum with expected values.
@@ -174,7 +159,9 @@ public class BucketInfo implements Serializable {
         return false;
       }
       IamConfiguration other = (IamConfiguration) o;
-      return Objects.equals(toPb(), other.toPb());
+      return Objects.equals(
+          Conversions.apiary().iamConfiguration().encode(this),
+          Conversions.apiary().iamConfiguration().encode(other));
     }
 
     @Override
@@ -228,42 +215,6 @@ public class BucketInfo implements Serializable {
       return publicAccessPrevention;
     }
 
-    Bucket.IamConfiguration toPb() {
-      Bucket.IamConfiguration iamConfiguration = new Bucket.IamConfiguration();
-
-      Bucket.IamConfiguration.UniformBucketLevelAccess uniformBucketLevelAccess =
-          new Bucket.IamConfiguration.UniformBucketLevelAccess();
-      uniformBucketLevelAccess.setEnabled(isUniformBucketLevelAccessEnabled);
-      uniformBucketLevelAccess.setLockedTime(
-          uniformBucketLevelAccessLockedTime == null
-              ? null
-              : new DateTime(uniformBucketLevelAccessLockedTime));
-
-      iamConfiguration.setUniformBucketLevelAccess(uniformBucketLevelAccess);
-      iamConfiguration.setPublicAccessPrevention(
-          publicAccessPrevention == null ? null : publicAccessPrevention.getValue());
-
-      return iamConfiguration;
-    }
-
-    static IamConfiguration fromPb(Bucket.IamConfiguration iamConfiguration) {
-      Bucket.IamConfiguration.UniformBucketLevelAccess uniformBucketLevelAccess =
-          iamConfiguration.getUniformBucketLevelAccess();
-      DateTime lockedTime = uniformBucketLevelAccess.getLockedTime();
-      String publicAccessPrevention = iamConfiguration.getPublicAccessPrevention();
-
-      PublicAccessPrevention publicAccessPreventionValue = null;
-      if (publicAccessPrevention != null) {
-        publicAccessPreventionValue = PublicAccessPrevention.parse(publicAccessPrevention);
-      }
-
-      return newBuilder()
-          .setIsUniformBucketLevelAccessEnabled(uniformBucketLevelAccess.getEnabled())
-          .setUniformBucketLevelAccessLockedTime(lockedTime == null ? null : lockedTime.getValue())
-          .setPublicAccessPrevention(publicAccessPreventionValue)
-          .build();
-    }
-
     /** Builder for {@code IamConfiguration} */
     public static class Builder {
       private Boolean isUniformBucketLevelAccessEnabled;
@@ -345,7 +296,9 @@ public class BucketInfo implements Serializable {
         return false;
       }
       Logging other = (Logging) o;
-      return Objects.equals(toPb(), other.toPb());
+      return Objects.equals(
+          Conversions.apiary().logging().encode(this),
+          Conversions.apiary().logging().encode(other));
     }
 
     @Override
@@ -372,25 +325,6 @@ public class BucketInfo implements Serializable {
       return logObjectPrefix;
     }
 
-    Bucket.Logging toPb() {
-      Bucket.Logging logging;
-      if (logBucket != null || logObjectPrefix != null) {
-        logging = new Bucket.Logging();
-        logging.setLogBucket(logBucket);
-        logging.setLogObjectPrefix(logObjectPrefix);
-      } else {
-        logging = Data.nullOf(Bucket.Logging.class);
-      }
-      return logging;
-    }
-
-    static Logging fromPb(Bucket.Logging logging) {
-      return newBuilder()
-          .setLogBucket(logging.getLogBucket())
-          .setLogObjectPrefix(logging.getLogObjectPrefix())
-          .build();
-    }
-
     private Logging(Builder builder) {
       this.logBucket = builder.logBucket;
       this.logObjectPrefix = builder.logObjectPrefix;
@@ -488,100 +422,17 @@ public class BucketInfo implements Serializable {
         return false;
       }
       final LifecycleRule other = (LifecycleRule) obj;
-      return Objects.equals(toPb(), other.toPb());
+      return Objects.equals(
+          Conversions.apiary().lifecycleRule().encode(this),
+          Conversions.apiary().lifecycleRule().encode(other));
     }
 
-    Rule toPb() {
-      Rule rule = new Rule();
-
-      Rule.Action action = new Rule.Action().setType(lifecycleAction.getActionType());
-      if (lifecycleAction.getActionType().equals(SetStorageClassLifecycleAction.TYPE)) {
-        action.setStorageClass(
-            ((SetStorageClassLifecycleAction) lifecycleAction).getStorageClass().toString());
-      }
-
-      rule.setAction(action);
-
-      Rule.Condition condition =
-          new Rule.Condition()
-              .setAge(lifecycleCondition.getAge())
-              .setCreatedBefore(
-                  lifecycleCondition.getCreatedBefore() == null
-                      ? null
-                      : new DateTime(true, lifecycleCondition.getCreatedBefore().getValue(), 0))
-              .setIsLive(lifecycleCondition.getIsLive())
-              .setNumNewerVersions(lifecycleCondition.getNumberOfNewerVersions())
-              .setMatchesStorageClass(
-                  lifecycleCondition.getMatchesStorageClass() == null
-                      ? null
-                      : transform(
-                          lifecycleCondition.getMatchesStorageClass(),
-                          Functions.toStringFunction()))
-              .setDaysSinceNoncurrentTime(lifecycleCondition.getDaysSinceNoncurrentTime())
-              .setNoncurrentTimeBefore(
-                  lifecycleCondition.getNoncurrentTimeBefore() == null
-                      ? null
-                      : new DateTime(
-                          true, lifecycleCondition.getNoncurrentTimeBefore().getValue(), 0))
-              .setCustomTimeBefore(
-                  lifecycleCondition.getCustomTimeBefore() == null
-                      ? null
-                      : new DateTime(true, lifecycleCondition.getCustomTimeBefore().getValue(), 0))
-              .setDaysSinceCustomTime(lifecycleCondition.getDaysSinceCustomTime());
-
-      rule.setCondition(condition);
-
-      return rule;
+    LifecycleAction getLifecycleAction() {
+      return lifecycleAction;
     }
 
-    static LifecycleRule fromPb(Rule rule) {
-      LifecycleAction lifecycleAction;
-
-      Rule.Action action = rule.getAction();
-
-      switch (action.getType()) {
-        case DeleteLifecycleAction.TYPE:
-          lifecycleAction = LifecycleAction.newDeleteAction();
-          break;
-        case SetStorageClassLifecycleAction.TYPE:
-          lifecycleAction =
-              LifecycleAction.newSetStorageClassAction(
-                  StorageClass.valueOf(action.getStorageClass()));
-          break;
-        default:
-          log.warning(
-              "The lifecycle action "
-                  + action.getType()
-                  + " is not supported by this version of the library. "
-                  + "Attempting to update with this rule may cause errors. Please "
-                  + "update to the latest version of google-cloud-storage.");
-          lifecycleAction = LifecycleAction.newLifecycleAction("Unknown action");
-      }
-
-      Rule.Condition condition = rule.getCondition();
-
-      LifecycleCondition.Builder conditionBuilder =
-          LifecycleCondition.newBuilder()
-              .setAge(condition.getAge())
-              .setCreatedBefore(condition.getCreatedBefore())
-              .setIsLive(condition.getIsLive())
-              .setNumberOfNewerVersions(condition.getNumNewerVersions())
-              .setMatchesStorageClass(
-                  condition.getMatchesStorageClass() == null
-                      ? null
-                      : transform(
-                          condition.getMatchesStorageClass(),
-                          new Function<String, StorageClass>() {
-                            public StorageClass apply(String storageClass) {
-                              return StorageClass.valueOf(storageClass);
-                            }
-                          }))
-              .setDaysSinceNoncurrentTime(condition.getDaysSinceNoncurrentTime())
-              .setNoncurrentTimeBefore(condition.getNoncurrentTimeBefore())
-              .setCustomTimeBefore(condition.getCustomTimeBefore())
-              .setDaysSinceCustomTime(condition.getDaysSinceCustomTime());
-
-      return new LifecycleRule(lifecycleAction, conditionBuilder.build());
+    LifecycleCondition getLifecycleCondition() {
+      return lifecycleCondition;
     }
 
     /**
@@ -902,7 +753,7 @@ public class BucketInfo implements Serializable {
   public abstract static class DeleteRule implements Serializable {
 
     private static final long serialVersionUID = 3137971668395933033L;
-    private static final String SUPPORTED_ACTION = "Delete";
+    static final String SUPPORTED_ACTION = "Delete";
     private final Type type;
 
     public enum Type {
@@ -935,42 +786,12 @@ public class BucketInfo implements Serializable {
         return false;
       }
       final DeleteRule other = (DeleteRule) obj;
-      return Objects.equals(toPb(), other.toPb());
-    }
-
-    Rule toPb() {
-      Rule rule = new Rule();
-      rule.setAction(new Rule.Action().setType(SUPPORTED_ACTION));
-      Rule.Condition condition = new Rule.Condition();
-      populateCondition(condition);
-      rule.setCondition(condition);
-      return rule;
+      return Objects.equals(
+          Conversions.apiary().deleteRule().encode(this),
+          Conversions.apiary().deleteRule().encode(other));
     }
 
     abstract void populateCondition(Rule.Condition condition);
-
-    static DeleteRule fromPb(Rule rule) {
-      if (rule.getAction() != null && SUPPORTED_ACTION.endsWith(rule.getAction().getType())) {
-        Rule.Condition condition = rule.getCondition();
-        Integer age = condition.getAge();
-        if (age != null) {
-          return new AgeDeleteRule(age);
-        }
-        DateTime dateTime = condition.getCreatedBefore();
-        if (dateTime != null) {
-          return new CreatedBeforeDeleteRule(dateTime.getValue());
-        }
-        Integer numNewerVersions = condition.getNumNewerVersions();
-        if (numNewerVersions != null) {
-          return new NumNewerVersionsDeleteRule(numNewerVersions);
-        }
-        Boolean isLive = condition.getIsLive();
-        if (isLive != null) {
-          return new IsLiveDeleteRule(isLive);
-        }
-      }
-      return new RawDeleteRule(rule);
-    }
   }
 
   /**
@@ -1041,8 +862,7 @@ public class BucketInfo implements Serializable {
       rule = new JacksonFactory().fromString(in.readUTF(), Rule.class);
     }
 
-    @Override
-    Rule toPb() {
+    Rule getRule() {
       return rule;
     }
   }
@@ -1894,12 +1714,19 @@ public class BucketInfo implements Serializable {
     return Objects.hash(name);
   }
 
+  // TODO: This equals and hashCode are broken. They don't validate the same properties!!!
   @Override
-  public boolean equals(Object obj) {
-    return obj == this
-        || obj != null
-            && obj.getClass().equals(BucketInfo.class)
-            && Objects.equals(toPb(), ((BucketInfo) obj).toPb());
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o.getClass().equals(BucketInfo.class))) {
+      return false;
+    }
+    BucketInfo that = (BucketInfo) o;
+    return Objects.equals(
+        Conversions.apiary().bucketInfo().encode(this),
+        Conversions.apiary().bucketInfo().encode(that));
   }
 
   @Override
@@ -1907,157 +1734,12 @@ public class BucketInfo implements Serializable {
     return MoreObjects.toStringHelper(this).add("name", name).toString();
   }
 
-  com.google.api.services.storage.model.Bucket toPb() {
-    com.google.api.services.storage.model.Bucket bucketPb =
-        new com.google.api.services.storage.model.Bucket();
-    bucketPb.setId(generatedId);
-    bucketPb.setName(name);
-    bucketPb.setEtag(etag);
-    if (createTime != null) {
-      bucketPb.setTimeCreated(new DateTime(createTime));
-    }
-    if (updateTime != null) {
-      bucketPb.setUpdated(new DateTime(updateTime));
-    }
-    if (metageneration != null) {
-      bucketPb.setMetageneration(metageneration);
-    }
-    if (location != null) {
-      bucketPb.setLocation(location);
-    }
-    if (locationType != null) {
-      bucketPb.setLocationType(locationType);
-    }
-    if (rpo != null) {
-      bucketPb.setRpo(rpo.toString());
-    }
-    if (storageClass != null) {
-      bucketPb.setStorageClass(storageClass.toString());
-    }
-    if (cors != null) {
-      bucketPb.setCors(transform(cors, Cors.TO_PB_FUNCTION));
-    }
-    if (acl != null) {
-      bucketPb.setAcl(
-          transform(
-              acl,
-              new Function<Acl, BucketAccessControl>() {
-                @Override
-                public BucketAccessControl apply(Acl acl) {
-                  return acl.toBucketPb();
-                }
-              }));
-    }
-    if (defaultAcl != null) {
-      bucketPb.setDefaultObjectAcl(
-          transform(
-              defaultAcl,
-              new Function<Acl, ObjectAccessControl>() {
-                @Override
-                public ObjectAccessControl apply(Acl acl) {
-                  return acl.toObjectPb();
-                }
-              }));
-    }
-    if (owner != null) {
-      bucketPb.setOwner(new Owner().setEntity(owner.toPb()));
-    }
-    bucketPb.setSelfLink(selfLink);
-    if (versioningEnabled != null) {
-      bucketPb.setVersioning(new Versioning().setEnabled(versioningEnabled));
-    }
-    if (requesterPays != null) {
-      Bucket.Billing billing = new Bucket.Billing();
-      billing.setRequesterPays(requesterPays);
-      bucketPb.setBilling(billing);
-    }
-    if (indexPage != null || notFoundPage != null) {
-      Website website = new Website();
-      website.setMainPageSuffix(indexPage);
-      website.setNotFoundPage(notFoundPage);
-      bucketPb.setWebsite(website);
-    }
-
-    if (deleteRules != null || lifecycleRules != null) {
-      Lifecycle lifecycle = new Lifecycle();
-
-      // Here we determine if we need to "clear" any defined Lifecycle rules by explicitly setting
-      // the Rule list of lifecycle to the empty list.
-      // In order for us to clear the rules, one of the three following must be true:
-      //   1. deleteRules is null while lifecycleRules is non-null and empty
-      //   2. lifecycleRules is null while deleteRules is non-null and empty
-      //   3. lifecycleRules is non-null and empty while deleteRules is non-null and empty
-      // If none of the above three is true, we will interpret as the Lifecycle rules being
-      // updated to the defined set of DeleteRule and LifecycleRule.
-      if ((deleteRules == null && lifecycleRules.isEmpty())
-          || (lifecycleRules == null && deleteRules.isEmpty())
-          || (deleteRules != null && deleteRules.isEmpty() && lifecycleRules.isEmpty())) {
-        lifecycle.setRule(Collections.<Rule>emptyList());
-      } else {
-        Set<Rule> rules = new HashSet<>();
-        if (deleteRules != null) {
-          rules.addAll(
-              transform(
-                  deleteRules,
-                  new Function<DeleteRule, Rule>() {
-                    @Override
-                    public Rule apply(DeleteRule deleteRule) {
-                      return deleteRule.toPb();
-                    }
-                  }));
-        }
-        if (lifecycleRules != null) {
-          rules.addAll(
-              transform(
-                  lifecycleRules,
-                  new Function<LifecycleRule, Rule>() {
-                    @Override
-                    public Rule apply(LifecycleRule lifecycleRule) {
-                      return lifecycleRule.toPb();
-                    }
-                  }));
-        }
-
-        if (!rules.isEmpty()) {
-          lifecycle.setRule(ImmutableList.copyOf(rules));
-        }
-      }
-
-      bucketPb.setLifecycle(lifecycle);
-    }
-
-    if (labels != null) {
-      bucketPb.setLabels(labels);
-    }
-    if (defaultKmsKeyName != null) {
-      bucketPb.setEncryption(new Encryption().setDefaultKmsKeyName(defaultKmsKeyName));
-    }
-    if (defaultEventBasedHold != null) {
-      bucketPb.setDefaultEventBasedHold(defaultEventBasedHold);
-    }
-    if (retentionPeriod != null) {
-      if (Data.isNull(retentionPeriod)) {
-        bucketPb.setRetentionPolicy(
-            Data.<Bucket.RetentionPolicy>nullOf(Bucket.RetentionPolicy.class));
-      } else {
-        Bucket.RetentionPolicy retentionPolicy = new Bucket.RetentionPolicy();
-        retentionPolicy.setRetentionPeriod(retentionPeriod);
-        if (retentionEffectiveTime != null) {
-          retentionPolicy.setEffectiveTime(new DateTime(retentionEffectiveTime));
-        }
-        if (retentionPolicyIsLocked != null) {
-          retentionPolicy.setIsLocked(retentionPolicyIsLocked);
-        }
-        bucketPb.setRetentionPolicy(retentionPolicy);
-      }
-    }
-    if (iamConfiguration != null) {
-      bucketPb.setIamConfiguration(iamConfiguration.toPb());
-    }
-    if (logging != null) {
-      bucketPb.setLogging(logging.toPb());
-    }
-    return bucketPb;
+  /**
+   * Attach this instance to an instance of {@link Storage} thereby allowing RPCs to be performed
+   * using the methods from the resulting {@link Bucket}
+   */
+  Bucket asBucket(Storage storage) {
+    return new Bucket(storage, new BucketInfo.BuilderImpl(this));
   }
 
   /** Creates a {@code BucketInfo} object for the provided bucket name. */
@@ -2069,134 +1751,4 @@ public class BucketInfo implements Serializable {
   public static Builder newBuilder(String name) {
     return new BuilderImpl(name);
   }
-
-  static BucketInfo fromPb(com.google.api.services.storage.model.Bucket bucketPb) {
-    Builder builder = new BuilderImpl(bucketPb.getName());
-    if (bucketPb.getId() != null) {
-      builder.setGeneratedId(bucketPb.getId());
-    }
-
-    if (bucketPb.getEtag() != null) {
-      builder.setEtag(bucketPb.getEtag());
-    }
-    if (bucketPb.getMetageneration() != null) {
-      builder.setMetageneration(bucketPb.getMetageneration());
-    }
-    if (bucketPb.getSelfLink() != null) {
-      builder.setSelfLink(bucketPb.getSelfLink());
-    }
-    if (bucketPb.getTimeCreated() != null) {
-      builder.setCreateTime(bucketPb.getTimeCreated().getValue());
-    }
-    if (bucketPb.getUpdated() != null) {
-      builder.setUpdateTime(bucketPb.getUpdated().getValue());
-    }
-    if (bucketPb.getLocation() != null) {
-      builder.setLocation(bucketPb.getLocation());
-    }
-    if (bucketPb.getRpo() != null) {
-      builder.setRpo(Rpo.valueOf(bucketPb.getRpo()));
-    }
-    if (bucketPb.getStorageClass() != null) {
-      builder.setStorageClass(StorageClass.valueOf(bucketPb.getStorageClass()));
-    }
-    if (bucketPb.getCors() != null) {
-      builder.setCors(transform(bucketPb.getCors(), Cors.FROM_PB_FUNCTION));
-    }
-    if (bucketPb.getAcl() != null) {
-      builder.setAcl(
-          transform(
-              bucketPb.getAcl(),
-              new Function<BucketAccessControl, Acl>() {
-                @Override
-                public Acl apply(BucketAccessControl bucketAccessControl) {
-                  return Acl.fromPb(bucketAccessControl);
-                }
-              }));
-    }
-    if (bucketPb.getDefaultObjectAcl() != null) {
-      builder.setDefaultAcl(
-          transform(
-              bucketPb.getDefaultObjectAcl(),
-              new Function<ObjectAccessControl, Acl>() {
-                @Override
-                public Acl apply(ObjectAccessControl objectAccessControl) {
-                  return Acl.fromPb(objectAccessControl);
-                }
-              }));
-    }
-    if (bucketPb.getOwner() != null) {
-      builder.setOwner(Entity.fromPb(bucketPb.getOwner().getEntity()));
-    }
-    if (bucketPb.getVersioning() != null) {
-      builder.setVersioningEnabled(bucketPb.getVersioning().getEnabled());
-    }
-    Website website = bucketPb.getWebsite();
-    if (website != null) {
-      builder.setIndexPage(website.getMainPageSuffix());
-      builder.setNotFoundPage(website.getNotFoundPage());
-    }
-    if (bucketPb.getLifecycle() != null && bucketPb.getLifecycle().getRule() != null) {
-      builder.setLifecycleRules(
-          transform(
-              bucketPb.getLifecycle().getRule(),
-              new Function<Rule, LifecycleRule>() {
-                @Override
-                public LifecycleRule apply(Rule rule) {
-                  return LifecycleRule.fromPb(rule);
-                }
-              }));
-      builder.setDeleteRules(
-          transform(
-              bucketPb.getLifecycle().getRule(),
-              new Function<Rule, DeleteRule>() {
-                @Override
-                public DeleteRule apply(Rule rule) {
-                  return DeleteRule.fromPb(rule);
-                }
-              }));
-    }
-    if (bucketPb.getLabels() != null) {
-      builder.setLabels(bucketPb.getLabels());
-    }
-    Bucket.Billing billing = bucketPb.getBilling();
-    if (billing != null) {
-      builder.setRequesterPays(billing.getRequesterPays());
-    }
-    Encryption encryption = bucketPb.getEncryption();
-    if (encryption != null
-        && encryption.getDefaultKmsKeyName() != null
-        && !encryption.getDefaultKmsKeyName().isEmpty()) {
-      builder.setDefaultKmsKeyName(encryption.getDefaultKmsKeyName());
-    }
-    if (bucketPb.getDefaultEventBasedHold() != null) {
-      builder.setDefaultEventBasedHold(bucketPb.getDefaultEventBasedHold());
-    }
-    Bucket.RetentionPolicy retentionPolicy = bucketPb.getRetentionPolicy();
-    if (retentionPolicy != null) {
-      if (retentionPolicy.getEffectiveTime() != null) {
-        builder.setRetentionEffectiveTime(retentionPolicy.getEffectiveTime().getValue());
-      }
-      if (retentionPolicy.getIsLocked() != null) {
-        builder.setRetentionPolicyIsLocked(retentionPolicy.getIsLocked());
-      }
-      if (retentionPolicy.getRetentionPeriod() != null) {
-        builder.setRetentionPeriod(retentionPolicy.getRetentionPeriod());
-      }
-    }
-    Bucket.IamConfiguration iamConfiguration = bucketPb.getIamConfiguration();
-
-    if (bucketPb.getLocationType() != null) {
-      builder.setLocationType(bucketPb.getLocationType());
-    }
-
-    if (iamConfiguration != null) {
-      builder.setIamConfiguration(IamConfiguration.fromPb(iamConfiguration));
-    }
-    Bucket.Logging logging = bucketPb.getLogging();
-    if (logging != null) {
-      builder.setLogging(Logging.fromPb(logging));
-    }
-    return builder.build();
-  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Conversions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Conversions.java
new file mode 100644
index 00000000..c6d1b8cb
--- /dev/null
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Conversions.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed 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 com.google.cloud.storage;
+
+final class Conversions {
+
+  private Conversions() {}
+
+  static ApiaryConversions apiary() {
+    return ApiaryConversions.INSTANCE;
+  }
+
+  @FunctionalInterface
+  interface Encoder<From, To> {
+    To encode(From f);
+  }
+
+  @FunctionalInterface
+  interface Decoder<From, To> {
+    To decode(From f);
+  }
+
+  interface Codec<A, B> extends Encoder<A, B>, Decoder<B, A> {
+    static <X, Y> Codec<X, Y> of(Encoder<X, Y> e, Decoder<Y, X> d) {
+      return new SimpleCodec<>(e, d);
+    }
+  }
+
+  private static final class SimpleCodec<A, B> implements Codec<A, B> {
+    private final Encoder<A, B> e;
+    private final Decoder<B, A> d;
+
+    private SimpleCodec(Encoder<A, B> e, Decoder<B, A> d) {
+      this.e = e;
+      this.d = d;
+    }
+
+    @Override
+    public B encode(A f) {
+      return e.encode(f);
+    }
+
+    @Override
+    public A decode(B f) {
+      return d.decode(f);
+    }
+  }
+}
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/CopyWriter.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/CopyWriter.java
index d621d149..44d733ce 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/CopyWriter.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/CopyWriter.java
@@ -71,7 +71,8 @@ public class CopyWriter implements Restorable<CopyWriter> {
     while (!isDone()) {
       copyChunk();
     }
-    return Blob.fromPb(serviceOptions.getService(), rewriteResponse.result);
+    BlobInfo info = Conversions.apiary().blobInfo().decode(rewriteResponse.result);
+    return info.asBlob(serviceOptions.getService());
   }
 
   /** Returns the size of the blob being copied. */
@@ -111,12 +112,15 @@ public class CopyWriter implements Restorable<CopyWriter> {
   public RestorableState<CopyWriter> capture() {
     return StateImpl.newBuilder(
             serviceOptions,
-            BlobId.fromPb(rewriteResponse.rewriteRequest.source),
+            Conversions.apiary().blobId().decode(rewriteResponse.rewriteRequest.source),
             rewriteResponse.rewriteRequest.sourceOptions,
             rewriteResponse.rewriteRequest.overrideInfo,
-            BlobInfo.fromPb(rewriteResponse.rewriteRequest.target),
+            Conversions.apiary().blobInfo().decode(rewriteResponse.rewriteRequest.target),
             rewriteResponse.rewriteRequest.targetOptions)
-        .setResult(rewriteResponse.result != null ? BlobInfo.fromPb(rewriteResponse.result) : null)
+        .setResult(
+            rewriteResponse.result != null
+                ? Conversions.apiary().blobInfo().decode(rewriteResponse.result)
+                : null)
         .setBlobSize(getBlobSize())
         .setIsDone(isDone())
         .setMegabytesCopiedPerChunk(rewriteResponse.rewriteRequest.megabytesRewrittenPerCall)
@@ -236,16 +240,16 @@ public class CopyWriter implements Restorable<CopyWriter> {
     public CopyWriter restore() {
       RewriteRequest rewriteRequest =
           new RewriteRequest(
-              source.toPb(),
+              Conversions.apiary().blobId().encode(source),
               sourceOptions,
               overrideInfo,
-              target.toPb(),
+              Conversions.apiary().blobInfo().encode(target),
               targetOptions,
               megabytesCopiedPerChunk);
       RewriteResponse rewriteResponse =
           new RewriteResponse(
               rewriteRequest,
-              result != null ? result.toPb() : null,
+              result != null ? Conversions.apiary().blobInfo().encode(result) : null,
               blobSize,
               isDone,
               rewriteToken,
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Cors.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Cors.java
index 18e8dfc8..0df26edd 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Cors.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Cors.java
@@ -17,12 +17,7 @@
 package com.google.cloud.storage;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Iterables.transform;
-import static com.google.common.collect.Lists.newArrayList;
 
-import com.google.api.services.storage.model.Bucket;
-import com.google.common.base.Function;
-import com.google.common.base.Functions;
 import com.google.common.collect.ImmutableList;
 import java.io.Serializable;
 import java.net.URI;
@@ -40,22 +35,6 @@ public final class Cors implements Serializable {
 
   private static final long serialVersionUID = -8637770919343335655L;
 
-  static final Function<Bucket.Cors, Cors> FROM_PB_FUNCTION =
-      new Function<Bucket.Cors, Cors>() {
-        @Override
-        public Cors apply(Bucket.Cors pb) {
-          return Cors.fromPb(pb);
-        }
-      };
-
-  static final Function<Cors, Bucket.Cors> TO_PB_FUNCTION =
-      new Function<Cors, Bucket.Cors>() {
-        @Override
-        public Bucket.Cors apply(Cors cors) {
-          return cors.toPb();
-        }
-      };
-
   private final Integer maxAgeSeconds;
   private final ImmutableList<HttpMethod> methods;
   private final ImmutableList<Origin> origins;
@@ -222,45 +201,4 @@ public final class Cors implements Serializable {
   public static Builder newBuilder() {
     return new Builder();
   }
-
-  Bucket.Cors toPb() {
-    Bucket.Cors pb = new Bucket.Cors();
-    pb.setMaxAgeSeconds(maxAgeSeconds);
-    pb.setResponseHeader(responseHeaders);
-    if (methods != null) {
-      pb.setMethod(newArrayList(transform(methods, Functions.toStringFunction())));
-    }
-    if (origins != null) {
-      pb.setOrigin(newArrayList(transform(origins, Functions.toStringFunction())));
-    }
-    return pb;
-  }
-
-  static Cors fromPb(Bucket.Cors cors) {
-    Builder builder = newBuilder().setMaxAgeSeconds(cors.getMaxAgeSeconds());
-    if (cors.getMethod() != null) {
-      builder.setMethods(
-          transform(
-              cors.getMethod(),
-              new Function<String, HttpMethod>() {
-                @Override
-                public HttpMethod apply(String name) {
-                  return HttpMethod.valueOf(name.toUpperCase());
-                }
-              }));
-    }
-    if (cors.getOrigin() != null) {
-      builder.setOrigins(
-          transform(
-              cors.getOrigin(),
-              new Function<String, Origin>() {
-                @Override
-                public Origin apply(String value) {
-                  return Origin.of(value);
-                }
-              }));
-    }
-    builder.setResponseHeaders(cors.getResponseHeader());
-    return builder.build();
-  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java
index c1331446..5291869d 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java
@@ -16,7 +16,6 @@
 
 package com.google.cloud.storage;
 
-import com.google.api.client.util.DateTime;
 import java.io.Serializable;
 import java.util.Objects;
 
@@ -77,33 +76,16 @@ public class HmacKey implements Serializable {
   }
 
   @Override
-  public boolean equals(Object obj) {
-    if (this == obj) {
+  public boolean equals(Object o) {
+    if (this == o) {
       return true;
     }
-    if (obj == null || getClass() != obj.getClass()) {
+    if (!(o instanceof HmacKey)) {
       return false;
     }
-    final HmacKeyMetadata other = (HmacKeyMetadata) obj;
-    return Objects.equals(this.secretKey, secretKey) && Objects.equals(this.metadata, metadata);
-  }
-
-  com.google.api.services.storage.model.HmacKey toPb() {
-    com.google.api.services.storage.model.HmacKey hmacKey =
-        new com.google.api.services.storage.model.HmacKey();
-    hmacKey.setSecret(this.secretKey);
-
-    if (metadata != null) {
-      hmacKey.setMetadata(metadata.toPb());
-    }
-
-    return hmacKey;
-  }
-
-  static HmacKey fromPb(com.google.api.services.storage.model.HmacKey hmacKey) {
-    return HmacKey.newBuilder(hmacKey.getSecret())
-        .setMetadata(HmacKeyMetadata.fromPb(hmacKey.getMetadata()))
-        .build();
+    HmacKey hmacKey = (HmacKey) o;
+    return Objects.equals(secretKey, hmacKey.secretKey)
+        && Objects.equals(metadata, hmacKey.metadata);
   }
 
   public enum HmacKeyState {
@@ -182,34 +164,6 @@ public class HmacKey implements Serializable {
           && Objects.equals(this.updateTime, other.updateTime);
     }
 
-    public com.google.api.services.storage.model.HmacKeyMetadata toPb() {
-      com.google.api.services.storage.model.HmacKeyMetadata metadata =
-          new com.google.api.services.storage.model.HmacKeyMetadata();
-      metadata.setAccessId(this.accessId);
-      metadata.setEtag(this.etag);
-      metadata.setId(this.id);
-      metadata.setProjectId(this.projectId);
-      metadata.setServiceAccountEmail(
-          this.serviceAccount == null ? null : this.serviceAccount.getEmail());
-      metadata.setState(this.state == null ? null : this.state.toString());
-      metadata.setTimeCreated(this.createTime == null ? null : new DateTime(this.createTime));
-      metadata.setUpdated(this.updateTime == null ? null : new DateTime(this.updateTime));
-
-      return metadata;
-    }
-
-    static HmacKeyMetadata fromPb(com.google.api.services.storage.model.HmacKeyMetadata metadata) {
-      return newBuilder(ServiceAccount.of(metadata.getServiceAccountEmail()))
-          .setAccessId(metadata.getAccessId())
-          .setCreateTime(metadata.getTimeCreated().getValue())
-          .setEtag(metadata.getEtag())
-          .setId(metadata.getId())
-          .setProjectId(metadata.getProjectId())
-          .setState(HmacKeyState.valueOf(metadata.getState()))
-          .setUpdateTime(metadata.getUpdated().getValue())
-          .build();
-    }
-
     /**
      * Returns the access id for this HMAC key. This is the id needed to get or delete the key. *
      */
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Notification.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Notification.java
index 7f9107a5..8179914d 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Notification.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Notification.java
@@ -122,7 +122,9 @@ public class Notification extends NotificationInfo {
       return false;
     }
     Notification notification = (Notification) o;
-    return Objects.equals(toPb(), notification.toPb())
+    return Objects.equals(
+            Conversions.apiary().notificationInfo().encode(this),
+            Conversions.apiary().notificationInfo().encode(notification))
         && Objects.equals(options, notification.options);
   }
 
@@ -130,10 +132,4 @@ public class Notification extends NotificationInfo {
   public int hashCode() {
     return Objects.hash(super.hashCode(), options, storage);
   }
-
-  static Notification fromPb(
-      Storage storage, com.google.api.services.storage.model.Notification notificationPb) {
-    return new Notification(
-        storage, new NotificationInfo.BuilderImpl(NotificationInfo.fromPb(notificationPb)));
-  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/NotificationInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/NotificationInfo.java
index df986ce2..1b60ed89 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/NotificationInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/NotificationInfo.java
@@ -19,11 +19,9 @@ package com.google.cloud.storage;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import com.google.api.pathtemplate.PathTemplate;
-import com.google.common.base.Function;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableMap;
 import java.io.Serializable;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -35,23 +33,6 @@ public class NotificationInfo implements Serializable {
   private static final long serialVersionUID = 5725883368559753810L;
   private static final PathTemplate PATH_TEMPLATE =
       PathTemplate.createWithoutUrlEncoding("projects/{project}/topics/{topic}");
-  static final Function<com.google.api.services.storage.model.Notification, NotificationInfo>
-      FROM_PB_FUNCTION =
-          new Function<com.google.api.services.storage.model.Notification, NotificationInfo>() {
-            @Override
-            public NotificationInfo apply(com.google.api.services.storage.model.Notification pb) {
-              return NotificationInfo.fromPb(pb);
-            }
-          };
-  static final Function<NotificationInfo, com.google.api.services.storage.model.Notification>
-      TO_PB_FUNCTION =
-          new Function<NotificationInfo, com.google.api.services.storage.model.Notification>() {
-            @Override
-            public com.google.api.services.storage.model.Notification apply(
-                NotificationInfo NotificationInfo) {
-              return NotificationInfo.toPb();
-            }
-          };
 
   public enum PayloadFormat {
     JSON_API_V1,
@@ -246,7 +227,7 @@ public class NotificationInfo implements Serializable {
 
   @Override
   public int hashCode() {
-    return toPb().hashCode();
+    return Conversions.apiary().notificationInfo().encode(this).hashCode();
   }
 
   @Override
@@ -254,7 +235,9 @@ public class NotificationInfo implements Serializable {
     return obj == this
         || obj != null
             && obj.getClass().equals(NotificationInfo.class)
-            && Objects.equals(toPb(), ((NotificationInfo) obj).toPb());
+            && Objects.equals(
+                Conversions.apiary().notificationInfo().encode(this),
+                Conversions.apiary().notificationInfo().encode(((NotificationInfo) obj)));
   }
 
   @Override
@@ -262,37 +245,6 @@ public class NotificationInfo implements Serializable {
     return MoreObjects.toStringHelper(this).add("topic", topic).toString();
   }
 
-  com.google.api.services.storage.model.Notification toPb() {
-    com.google.api.services.storage.model.Notification notificationPb =
-        new com.google.api.services.storage.model.Notification();
-    if (notificationId != null) {
-      notificationPb.setId(notificationId);
-    }
-    notificationPb.setEtag(etag);
-    if (customAttributes != null) {
-      notificationPb.setCustomAttributes(customAttributes);
-    }
-    if (eventTypes != null && eventTypes.size() > 0) {
-      List<String> eventTypesPb = new ArrayList<>();
-      for (EventType eventType : eventTypes) {
-        eventTypesPb.add(eventType.toString());
-      }
-      notificationPb.setEventTypes(eventTypesPb);
-    }
-    if (objectNamePrefix != null) {
-      notificationPb.setObjectNamePrefix(objectNamePrefix);
-    }
-    if (payloadFormat != null) {
-      notificationPb.setPayloadFormat(payloadFormat.toString());
-    } else {
-      notificationPb.setPayloadFormat(PayloadFormat.NONE.toString());
-    }
-    notificationPb.setSelfLink(selfLink);
-    notificationPb.setTopic(topic);
-
-    return notificationPb;
-  }
-
   /**
    * Creates a {@code NotificationInfo} object for the provided topic.
    *
@@ -324,39 +276,11 @@ public class NotificationInfo implements Serializable {
     return new BuilderImpl(this);
   }
 
-  static NotificationInfo fromPb(
-      com.google.api.services.storage.model.Notification notificationPb) {
-    NotificationInfo.Builder builder = new NotificationInfo.BuilderImpl(notificationPb.getTopic());
-    if (notificationPb.getId() != null) {
-      builder.setNotificationId(notificationPb.getId());
-    }
-    if (notificationPb.getEtag() != null) {
-      builder.setEtag(notificationPb.getEtag());
-    }
-    if (notificationPb.getCustomAttributes() != null) {
-      builder.setCustomAttributes(notificationPb.getCustomAttributes());
-    }
-    if (notificationPb.getSelfLink() != null) {
-      builder.setSelfLink(notificationPb.getSelfLink());
-    }
-    if (notificationPb.getObjectNamePrefix() != null) {
-      builder.setObjectNamePrefix(notificationPb.getObjectNamePrefix());
-    }
-    if (notificationPb.getEventTypes() != null) {
-      List<String> eventTypesPb = notificationPb.getEventTypes();
-      EventType[] eventTypes = new EventType[eventTypesPb.size()];
-      for (int index = 0; index < eventTypesPb.size(); index++) {
-        eventTypes[index] = EventType.valueOf(eventTypesPb.get(index));
-      }
-      builder.setEventTypes(eventTypes);
-    }
-    if (notificationPb.getPayloadFormat() != null) {
-      builder.setPayloadFormat(PayloadFormat.valueOf(notificationPb.getPayloadFormat()));
-    }
-    return builder.build();
+  Notification asNotification(Storage storage) {
+    return new Notification(storage, new BuilderImpl(this));
   }
 
-  private static void checkTopicFormat(String topic) {
+  private static void checkTopicFormat(String topic) { // todo: why does this exist?
     PATH_TEMPLATE.validatedMatch(topic, "topic name must be in valid format");
   }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableMedia.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableMedia.java
index 9f1e1900..ad14ba3d 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableMedia.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableMedia.java
@@ -34,7 +34,10 @@ final class ResumableMedia {
         Retrying.run(
             storageOptions,
             algorithm,
-            () -> storageOptions.getStorageRpcV1().open(blob.toPb(), optionsMap),
+            () ->
+                storageOptions
+                    .getStorageRpcV1()
+                    .open(Conversions.apiary().blobInfo().encode(blob), optionsMap),
             Function.identity());
   }
 
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ServiceAccount.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ServiceAccount.java
index 33ae28b6..b3a7b0e2 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ServiceAccount.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ServiceAccount.java
@@ -16,7 +16,6 @@
 
 package com.google.cloud.storage;
 
-import com.google.common.base.Function;
 import com.google.common.base.MoreObjects;
 import java.io.Serializable;
 import java.util.Objects;
@@ -29,24 +28,6 @@ import java.util.Objects;
  */
 public final class ServiceAccount implements Serializable {
 
-  static final Function<com.google.api.services.storage.model.ServiceAccount, ServiceAccount>
-      FROM_PB_FUNCTION =
-          new Function<com.google.api.services.storage.model.ServiceAccount, ServiceAccount>() {
-            @Override
-            public ServiceAccount apply(com.google.api.services.storage.model.ServiceAccount pb) {
-              return ServiceAccount.fromPb(pb);
-            }
-          };
-  static final Function<ServiceAccount, com.google.api.services.storage.model.ServiceAccount>
-      TO_PB_FUNCTION =
-          new Function<ServiceAccount, com.google.api.services.storage.model.ServiceAccount>() {
-            @Override
-            public com.google.api.services.storage.model.ServiceAccount apply(
-                ServiceAccount metadata) {
-              return metadata.toPb();
-            }
-          };
-
   private static final long serialVersionUID = 4199610694227857331L;
 
   private final String email;
@@ -72,23 +53,19 @@ public final class ServiceAccount implements Serializable {
 
   @Override
   public boolean equals(Object obj) {
-    return obj == this
-        || obj instanceof ServiceAccount && Objects.equals(toPb(), ((ServiceAccount) obj).toPb());
-  }
-
-  com.google.api.services.storage.model.ServiceAccount toPb() {
-    com.google.api.services.storage.model.ServiceAccount serviceAccountPb =
-        new com.google.api.services.storage.model.ServiceAccount();
-    serviceAccountPb.setEmailAddress(email);
-    return serviceAccountPb;
+    if (obj == this) {
+      return true;
+    }
+    if (!(obj instanceof ServiceAccount)) {
+      return false;
+    }
+    return Objects.equals(
+        Conversions.apiary().serviceAccount().encode(this),
+        Conversions.apiary().serviceAccount().encode((ServiceAccount) obj));
   }
 
   /** Returns a {@code ServiceAccount} object for the provided email. */
   public static ServiceAccount of(String email) {
     return new ServiceAccount(email);
   }
-
-  static ServiceAccount fromPb(com.google.api.services.storage.model.ServiceAccount accountPb) {
-    return new ServiceAccount(accountPb.getEmailAddress());
-  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageBatch.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageBatch.java
index 9f5f84b2..d3814871 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageBatch.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageBatch.java
@@ -100,7 +100,7 @@ public class StorageBatch {
     StorageBatchResult<Boolean> result = new StorageBatchResult<>();
     RpcBatch.Callback<Void> callback = createDeleteCallback(result);
     Map<StorageRpc.Option, ?> optionMap = StorageImpl.optionMap(blob, options);
-    batch.addDelete(blob.toPb(), callback, optionMap);
+    batch.addDelete(Conversions.apiary().blobId().encode(blob), callback, optionMap);
     return result;
   }
 
@@ -114,7 +114,7 @@ public class StorageBatch {
     StorageBatchResult<Blob> result = new StorageBatchResult<>();
     RpcBatch.Callback<StorageObject> callback = createUpdateCallback(this.options, result);
     Map<StorageRpc.Option, ?> optionMap = StorageImpl.optionMap(blobInfo, options);
-    batch.addPatch(blobInfo.toPb(), callback, optionMap);
+    batch.addPatch(Conversions.apiary().blobInfo().encode(blobInfo), callback, optionMap);
     return result;
   }
 
@@ -140,7 +140,7 @@ public class StorageBatch {
     StorageBatchResult<Blob> result = new StorageBatchResult<>();
     RpcBatch.Callback<StorageObject> callback = createGetCallback(this.options, result);
     Map<StorageRpc.Option, ?> optionMap = StorageImpl.optionMap(blob, options);
-    batch.addGet(blob.toPb(), callback, optionMap);
+    batch.addGet(Conversions.apiary().blobId().encode(blob), callback, optionMap);
     return result;
   }
 
@@ -173,8 +173,8 @@ public class StorageBatch {
     return new RpcBatch.Callback<StorageObject>() {
       @Override
       public void onSuccess(StorageObject response) {
-        result.success(
-            response == null ? null : Blob.fromPb(serviceOptions.getService(), response));
+        BlobInfo info = Conversions.apiary().blobInfo().decode(response);
+        result.success(response == null ? null : info.asBlob(serviceOptions.getService()));
       }
 
       @Override
@@ -194,8 +194,8 @@ public class StorageBatch {
     return new RpcBatch.Callback<StorageObject>() {
       @Override
       public void onSuccess(StorageObject response) {
-        result.success(
-            response == null ? null : Blob.fromPb(serviceOptions.getService(), response));
+        BlobInfo info = Conversions.apiary().blobInfo().decode(response);
+        result.success(response == null ? null : info.asBlob(serviceOptions.getService()));
       }
 
       @Override
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
index bf372c88..4ee9c770 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
@@ -50,7 +50,6 @@ import com.google.cloud.RetryHelper.RetryHelperException;
 import com.google.cloud.Tuple;
 import com.google.cloud.WriteChannel;
 import com.google.cloud.storage.Acl.Entity;
-import com.google.cloud.storage.Blob.BlobSourceOption;
 import com.google.cloud.storage.HmacKey.HmacKeyMetadata;
 import com.google.cloud.storage.PostPolicyV4.ConditionV4Type;
 import com.google.cloud.storage.PostPolicyV4.PostConditionsV4;
@@ -112,6 +111,8 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
   private static final int DEFAULT_BUFFER_SIZE = 15 * 1024 * 1024;
   private static final int MIN_BUFFER_SIZE = 256 * 1024;
 
+  private static final ApiaryConversions codecs = Conversions.apiary();
+
   private final RetryAlgorithmManager retryAlgorithmManager;
   private final StorageRpc storageRpc;
 
@@ -123,12 +124,15 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) {
-    final com.google.api.services.storage.model.Bucket bucketPb = bucketInfo.toPb();
+    final com.google.api.services.storage.model.Bucket bucketPb =
+        codecs.bucketInfo().encode(bucketInfo);
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(bucketInfo, options);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForBucketsCreate(bucketPb, optionsMap);
     return run(
-        algorithm, () -> storageRpc.create(bucketPb, optionsMap), (b) -> Bucket.fromPb(this, b));
+        algorithm,
+        () -> storageRpc.create(bucketPb, optionsMap),
+        (b) -> Conversions.apiary().bucketInfo().decode(b).asBucket(this));
   }
 
   @Override
@@ -179,12 +183,16 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
   @Deprecated
   public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) {
     Tuple<BlobInfo, BlobTargetOption[]> targetOptions = BlobTargetOption.convert(blobInfo, options);
-    StorageObject blobPb = targetOptions.x().toPb();
+    StorageObject blobPb = codecs.blobInfo().encode(targetOptions.x());
     Map<StorageRpc.Option, ?> optionsMap = optionMap(targetOptions.x(), targetOptions.y());
     InputStream inputStreamParam =
         firstNonNull(content, new ByteArrayInputStream(EMPTY_BYTE_ARRAY));
     // retries are not safe when the input is an InputStream, so we can't retry.
-    return Blob.fromPb(this, storageRpc.create(blobPb, inputStreamParam, optionsMap));
+    BlobInfo info =
+        Conversions.apiary()
+            .blobInfo()
+            .decode(storageRpc.create(blobPb, inputStreamParam, optionsMap));
+    return info.asBlob(this);
   }
 
   private Blob internalCreate(
@@ -194,7 +202,7 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
       final int length,
       BlobTargetOption... options) {
     Preconditions.checkNotNull(content);
-    final StorageObject blobPb = info.toPb();
+    final StorageObject blobPb = codecs.blobInfo().encode(info);
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(info, options);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForObjectsCreate(blobPb, optionsMap);
@@ -203,7 +211,10 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
         () ->
             storageRpc.create(
                 blobPb, new ByteArrayInputStream(content, offset, length), optionsMap),
-        (x) -> Blob.fromPb(this, x));
+        (x) -> {
+          BlobInfo info1 = Conversions.apiary().blobInfo().decode(x);
+          return info1.asBlob(this);
+        });
   }
 
   @Override
@@ -240,7 +251,8 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
       uploadHelper(Channels.newChannel(content), writer, bufferSize);
     }
     StorageObject objectProto = blobWriteChannel.getStorageObject();
-    return Blob.fromPb(this, objectProto);
+    BlobInfo info = Conversions.apiary().blobInfo().decode(objectProto);
+    return info.asBlob(this);
   }
 
   /*
@@ -262,12 +274,15 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public Bucket get(String bucket, BucketGetOption... options) {
-    final com.google.api.services.storage.model.Bucket bucketPb = BucketInfo.of(bucket).toPb();
+    final com.google.api.services.storage.model.Bucket bucketPb =
+        codecs.bucketInfo().encode(BucketInfo.of(bucket));
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(options);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForBucketsGet(bucketPb, optionsMap);
     return run(
-        algorithm, () -> storageRpc.get(bucketPb, optionsMap), (b) -> Bucket.fromPb(this, b));
+        algorithm,
+        () -> storageRpc.get(bucketPb, optionsMap),
+        (b) -> Conversions.apiary().bucketInfo().decode(b).asBucket(this));
   }
 
   @Override
@@ -277,12 +292,17 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public Blob get(BlobId blob, BlobGetOption... options) {
-    final StorageObject storedObject = blob.toPb();
+    final StorageObject storedObject = codecs.blobId().encode(blob);
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(blob, options);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForObjectsGet(storedObject, optionsMap);
     return run(
-        algorithm, () -> storageRpc.get(storedObject, optionsMap), (x) -> Blob.fromPb(this, x));
+        algorithm,
+        () -> storageRpc.get(storedObject, optionsMap),
+        (x) -> {
+          BlobInfo info = Conversions.apiary().blobInfo().decode(x);
+          return info.asBlob(this);
+        });
   }
 
   @Override
@@ -379,7 +399,12 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
               result.y() == null
                   ? ImmutableList.of()
                   : Iterables.transform(
-                      result.y(), bucketPb -> Bucket.fromPb(serviceOptions.getService(), bucketPb));
+                      result.y(),
+                      bucketPb ->
+                          Conversions.apiary()
+                              .bucketInfo()
+                              .decode(bucketPb)
+                              .asBucket(serviceOptions.getService()));
           return new PageImpl<>(
               new BucketPageFetcher(serviceOptions, cursor, optionsMap), cursor, buckets);
         });
@@ -402,7 +427,10 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
                   ? ImmutableList.of()
                   : Iterables.transform(
                       result.y(),
-                      storageObject -> Blob.fromPb(serviceOptions.getService(), storageObject));
+                      storageObject -> {
+                        BlobInfo info = Conversions.apiary().blobInfo().decode(storageObject);
+                        return info.asBlob(serviceOptions.getService());
+                      });
           return new PageImpl<>(
               new BlobPageFetcher(bucket, serviceOptions, cursor, optionsMap), cursor, blobs);
         });
@@ -410,22 +438,30 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public Bucket update(BucketInfo bucketInfo, BucketTargetOption... options) {
-    final com.google.api.services.storage.model.Bucket bucketPb = bucketInfo.toPb();
+    final com.google.api.services.storage.model.Bucket bucketPb =
+        codecs.bucketInfo().encode(bucketInfo);
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(bucketInfo, options);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForBucketsUpdate(bucketPb, optionsMap);
     return run(
-        algorithm, () -> storageRpc.patch(bucketPb, optionsMap), (x) -> Bucket.fromPb(this, x));
+        algorithm,
+        () -> storageRpc.patch(bucketPb, optionsMap),
+        (x) -> Conversions.apiary().bucketInfo().decode(x).asBucket(this));
   }
 
   @Override
   public Blob update(BlobInfo blobInfo, BlobTargetOption... options) {
-    final StorageObject storageObject = blobInfo.toPb();
+    final StorageObject storageObject = codecs.blobInfo().encode(blobInfo);
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(blobInfo, options);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForObjectsUpdate(storageObject, optionsMap);
     return run(
-        algorithm, () -> storageRpc.patch(storageObject, optionsMap), (x) -> Blob.fromPb(this, x));
+        algorithm,
+        () -> storageRpc.patch(storageObject, optionsMap),
+        (x) -> {
+          BlobInfo info = Conversions.apiary().blobInfo().decode(x);
+          return info.asBlob(this);
+        });
   }
 
   @Override
@@ -435,7 +471,8 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public boolean delete(String bucket, BucketSourceOption... options) {
-    final com.google.api.services.storage.model.Bucket bucketPb = BucketInfo.of(bucket).toPb();
+    final com.google.api.services.storage.model.Bucket bucketPb =
+        codecs.bucketInfo().encode(BucketInfo.of(bucket));
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(options);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForBucketsDelete(bucketPb, optionsMap);
@@ -449,7 +486,7 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public boolean delete(BlobId blob, BlobSourceOption... options) {
-    final StorageObject storageObject = blob.toPb();
+    final StorageObject storageObject = codecs.blobId().encode(blob);
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(blob, options);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForObjectsDelete(storageObject, optionsMap);
@@ -467,15 +504,17 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
         Lists.newArrayListWithCapacity(composeRequest.getSourceBlobs().size());
     for (ComposeRequest.SourceBlob sourceBlob : composeRequest.getSourceBlobs()) {
       sources.add(
-          BlobInfo.newBuilder(
-                  BlobId.of(
-                      composeRequest.getTarget().getBucket(),
-                      sourceBlob.getName(),
-                      sourceBlob.getGeneration()))
-              .build()
-              .toPb());
+          codecs
+              .blobInfo()
+              .encode(
+                  BlobInfo.newBuilder(
+                          BlobId.of(
+                              composeRequest.getTarget().getBucket(),
+                              sourceBlob.getName(),
+                              sourceBlob.getGeneration()))
+                      .build()));
     }
-    final StorageObject target = composeRequest.getTarget().toPb();
+    final StorageObject target = codecs.blobInfo().encode(composeRequest.getTarget());
     final Map<StorageRpc.Option, ?> targetOptions =
         optionMap(
             composeRequest.getTarget().getGeneration(),
@@ -486,16 +525,19 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
     return run(
         algorithm,
         () -> storageRpc.compose(sources, target, targetOptions),
-        (x) -> Blob.fromPb(this, x));
+        (x) -> {
+          BlobInfo info = Conversions.apiary().blobInfo().decode(x);
+          return info.asBlob(this);
+        });
   }
 
   @Override
   public CopyWriter copy(final CopyRequest copyRequest) {
-    final StorageObject source = copyRequest.getSource().toPb();
+    final StorageObject source = codecs.blobId().encode(copyRequest.getSource());
     final Map<StorageRpc.Option, ?> sourceOptions =
         optionMap(
             copyRequest.getSource().getGeneration(), null, copyRequest.getSourceOptions(), true);
-    final StorageObject targetObject = copyRequest.getTarget().toPb();
+    final StorageObject targetObject = codecs.blobInfo().encode(copyRequest.getTarget());
     final Map<StorageRpc.Option, ?> targetOptions =
         optionMap(
             copyRequest.getTarget().getGeneration(),
@@ -523,7 +565,7 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public byte[] readAllBytes(BlobId blob, BlobSourceOption... options) {
-    final StorageObject storageObject = blob.toPb();
+    final StorageObject storageObject = codecs.blobId().encode(blob);
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(blob, options);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForObjectsGet(storageObject, optionsMap);
@@ -559,7 +601,7 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
   @Override
   public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options) {
     final CountingOutputStream countingOutputStream = new CountingOutputStream(outputStream);
-    final StorageObject pb = blob.toPb();
+    final StorageObject pb = codecs.blobId().encode(blob);
     final Map<StorageRpc.Option, ?> requestOptions = optionMap(blob, options);
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForObjectsGet(pb, requestOptions);
     Retrying.run(
@@ -1084,10 +1126,11 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public Acl getAcl(final String bucket, final Entity entity, BucketSourceOption... options) {
-    String pb = entity.toPb();
+    String pb = codecs.entity().encode(entity);
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(options);
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForBucketAclGet(pb, optionsMap);
-    return run(algorithm, () -> storageRpc.getAcl(bucket, pb, optionsMap), Acl::fromPb);
+    return run(
+        algorithm, () -> storageRpc.getAcl(bucket, pb, optionsMap), codecs.bucketAcl()::decode);
   }
 
   @Override
@@ -1098,7 +1141,7 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
   @Override
   public boolean deleteAcl(
       final String bucket, final Entity entity, BucketSourceOption... options) {
-    final String pb = entity.toPb();
+    final String pb = codecs.entity().encode(entity);
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(options);
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForBucketAclDelete(pb, optionsMap);
     return run(algorithm, () -> storageRpc.deleteAcl(bucket, pb, optionsMap), Function.identity());
@@ -1111,11 +1154,12 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public Acl createAcl(String bucket, Acl acl, BucketSourceOption... options) {
-    final BucketAccessControl aclPb = acl.toBucketPb().setBucket(bucket);
+    final BucketAccessControl aclPb = codecs.bucketAcl().encode(acl).setBucket(bucket);
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(options);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForBucketAclCreate(aclPb, optionsMap);
-    return run(algorithm, () -> storageRpc.createAcl(aclPb, optionsMap), Acl::fromPb);
+    return run(
+        algorithm, () -> storageRpc.createAcl(aclPb, optionsMap), codecs.bucketAcl()::decode);
   }
 
   @Override
@@ -1125,11 +1169,11 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public Acl updateAcl(String bucket, Acl acl, BucketSourceOption... options) {
-    final BucketAccessControl aclPb = acl.toBucketPb().setBucket(bucket);
+    final BucketAccessControl aclPb = codecs.bucketAcl().encode(acl).setBucket(bucket);
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(options);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForBucketAclUpdate(aclPb, optionsMap);
-    return run(algorithm, () -> storageRpc.patchAcl(aclPb, optionsMap), Acl::fromPb);
+    return run(algorithm, () -> storageRpc.patchAcl(aclPb, optionsMap), codecs.bucketAcl()::decode);
   }
 
   @Override
@@ -1147,7 +1191,9 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
         () -> storageRpc.listAcls(bucket, optionsMap),
         (answer) ->
             answer.stream()
-                .map(Acl.FROM_BUCKET_PB_FUNCTION)
+                .map(
+                    (com.google.common.base.Function<BucketAccessControl, Acl>)
+                        codecs.bucketAcl()::decode)
                 .collect(ImmutableList.toImmutableList()));
   }
 
@@ -1158,30 +1204,30 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public Acl getDefaultAcl(final String bucket, final Entity entity) {
-    String pb = entity.toPb();
+    String pb = codecs.entity().encode(entity);
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForDefaultObjectAclGet(pb);
-    return run(algorithm, () -> storageRpc.getDefaultAcl(bucket, pb), Acl::fromPb);
+    return run(algorithm, () -> storageRpc.getDefaultAcl(bucket, pb), codecs.objectAcl()::decode);
   }
 
   @Override
   public boolean deleteDefaultAcl(final String bucket, final Entity entity) {
-    String pb = entity.toPb();
+    String pb = codecs.entity().encode(entity);
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForDefaultObjectAclDelete(pb);
     return run(algorithm, () -> storageRpc.deleteDefaultAcl(bucket, pb), Function.identity());
   }
 
   @Override
   public Acl createDefaultAcl(String bucket, Acl acl) {
-    final ObjectAccessControl aclPb = acl.toObjectPb().setBucket(bucket);
+    final ObjectAccessControl aclPb = codecs.objectAcl().encode(acl).setBucket(bucket);
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForDefaultObjectAclCreate(aclPb);
-    return run(algorithm, () -> storageRpc.createDefaultAcl(aclPb), Acl::fromPb);
+    return run(algorithm, () -> storageRpc.createDefaultAcl(aclPb), codecs.objectAcl()::decode);
   }
 
   @Override
   public Acl updateDefaultAcl(String bucket, Acl acl) {
-    final ObjectAccessControl aclPb = acl.toObjectPb().setBucket(bucket);
+    final ObjectAccessControl aclPb = codecs.objectAcl().encode(acl).setBucket(bucket);
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForDefaultObjectAclUpdate(aclPb);
-    return run(algorithm, () -> storageRpc.patchDefaultAcl(aclPb), Acl::fromPb);
+    return run(algorithm, () -> storageRpc.patchDefaultAcl(aclPb), codecs.objectAcl()::decode);
   }
 
   @Override
@@ -1192,7 +1238,9 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
         () -> storageRpc.listDefaultAcls(bucket),
         (answer) ->
             answer.stream()
-                .map(Acl.FROM_OBJECT_PB_FUNCTION)
+                .map(
+                    (com.google.common.base.Function<ObjectAccessControl, Acl>)
+                        codecs.objectAcl()::decode)
                 .collect(ImmutableList.toImmutableList()));
   }
 
@@ -1201,10 +1249,13 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
     String bucket = blob.getBucket();
     String name = blob.getName();
     Long generation = blob.getGeneration();
-    String pb = entity.toPb();
+    String pb = codecs.entity().encode(entity);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForObjectAclGet(bucket, name, generation, pb);
-    return run(algorithm, () -> storageRpc.getAcl(bucket, name, generation, pb), Acl::fromPb);
+    return run(
+        algorithm,
+        () -> storageRpc.getAcl(bucket, name, generation, pb),
+        codecs.objectAcl()::decode);
   }
 
   @Override
@@ -1212,7 +1263,7 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
     String bucket = blob.getBucket();
     String name = blob.getName();
     Long generation = blob.getGeneration();
-    String pb = entity.toPb();
+    String pb = codecs.entity().encode(entity);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForObjectAclDelete(bucket, name, generation, pb);
     return run(
@@ -1222,23 +1273,27 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
   @Override
   public Acl createAcl(final BlobId blob, final Acl acl) {
     final ObjectAccessControl aclPb =
-        acl.toObjectPb()
+        codecs
+            .objectAcl()
+            .encode(acl)
             .setBucket(blob.getBucket())
             .setObject(blob.getName())
             .setGeneration(blob.getGeneration());
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForObjectAclCreate(aclPb);
-    return run(algorithm, () -> storageRpc.createAcl(aclPb), Acl::fromPb);
+    return run(algorithm, () -> storageRpc.createAcl(aclPb), codecs.objectAcl()::decode);
   }
 
   @Override
   public Acl updateAcl(BlobId blob, Acl acl) {
     final ObjectAccessControl aclPb =
-        acl.toObjectPb()
+        codecs
+            .objectAcl()
+            .encode(acl)
             .setBucket(blob.getBucket())
             .setObject(blob.getName())
             .setGeneration(blob.getGeneration());
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForObjectAclUpdate(aclPb);
-    return run(algorithm, () -> storageRpc.patchAcl(aclPb), Acl::fromPb);
+    return run(algorithm, () -> storageRpc.patchAcl(aclPb), codecs.objectAcl()::decode);
   }
 
   @Override
@@ -1253,7 +1308,9 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
         () -> storageRpc.listAcls(bucket, name, generation),
         (answer) ->
             answer.stream()
-                .map(Acl.FROM_OBJECT_PB_FUNCTION)
+                .map(
+                    (com.google.common.base.Function<ObjectAccessControl, Acl>)
+                        codecs.objectAcl()::decode)
                 .collect(ImmutableList.toImmutableList()));
   }
 
@@ -1262,7 +1319,7 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
     String pb = serviceAccount.getEmail();
     Map<StorageRpc.Option, ?> optionsMap = optionMap(options);
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForHmacKeyCreate(pb, optionsMap);
-    return run(algorithm, () -> storageRpc.createHmacKey(pb, optionsMap), HmacKey::fromPb);
+    return run(algorithm, () -> storageRpc.createHmacKey(pb, optionsMap), codecs.hmacKey()::decode);
   }
 
   @Override
@@ -1278,15 +1335,19 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
     return run(
         algorithm,
         () -> storageRpc.getHmacKey(accessId, optionMap(options)),
-        HmacKeyMetadata::fromPb);
+        codecs.hmacKeyMetadata()::decode);
   }
 
   private HmacKeyMetadata updateHmacKey(
       final HmacKeyMetadata hmacKeyMetadata, final UpdateHmacKeyOption... options) {
-    com.google.api.services.storage.model.HmacKeyMetadata pb = hmacKeyMetadata.toPb();
+    com.google.api.services.storage.model.HmacKeyMetadata pb =
+        codecs.hmacKeyMetadata().encode(hmacKeyMetadata);
     Map<StorageRpc.Option, ?> optionsMap = optionMap(options);
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForHmacKeyUpdate(pb, optionsMap);
-    return run(algorithm, () -> storageRpc.updateHmacKey(pb, optionsMap), HmacKeyMetadata::fromPb);
+    return run(
+        algorithm,
+        () -> storageRpc.updateHmacKey(pb, optionsMap),
+        codecs.hmacKeyMetadata()::decode);
   }
 
   @Override
@@ -1305,7 +1366,8 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public void deleteHmacKey(final HmacKeyMetadata metadata, final DeleteHmacKeyOption... options) {
-    com.google.api.services.storage.model.HmacKeyMetadata pb = metadata.toPb();
+    com.google.api.services.storage.model.HmacKeyMetadata pb =
+        codecs.hmacKeyMetadata().encode(metadata);
     Map<StorageRpc.Option, ?> optionsMap = optionMap(options);
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForHmacKeyDelete(pb, optionsMap);
     run(
@@ -1332,7 +1394,7 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
           final Iterable<HmacKeyMetadata> metadata =
               result.y() == null
                   ? ImmutableList.of()
-                  : Iterables.transform(result.y(), HmacKeyMetadata::fromPb);
+                  : Iterables.transform(result.y(), codecs.hmacKeyMetadata()::decode);
           return new PageImpl<>(
               new HmacKeyMetadataPageFetcher(serviceOptions, retryAlgorithmManager, options),
               cursor,
@@ -1386,20 +1448,22 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
   @Override
   public Bucket lockRetentionPolicy(BucketInfo bucketInfo, BucketTargetOption... options) {
-    final com.google.api.services.storage.model.Bucket bucketPb = bucketInfo.toPb();
+    final com.google.api.services.storage.model.Bucket bucketPb =
+        codecs.bucketInfo().encode(bucketInfo);
     final Map<StorageRpc.Option, ?> optionsMap = optionMap(bucketInfo, options);
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForBucketsLockRetentionPolicy(bucketPb, optionsMap);
     return run(
         algorithm,
         () -> storageRpc.lockRetentionPolicy(bucketPb, optionsMap),
-        (x) -> Bucket.fromPb(this, x));
+        (x) -> Conversions.apiary().bucketInfo().decode(x).asBucket(this));
   }
 
   @Override
   public ServiceAccount getServiceAccount(final String projectId) {
     ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForServiceAccountGet(projectId);
-    return run(algorithm, () -> storageRpc.getServiceAccount(projectId), ServiceAccount::fromPb);
+    return run(
+        algorithm, () -> storageRpc.getServiceAccount(projectId), codecs.serviceAccount()::decode);
   }
 
   private <T, U> U run(ResultRetryAlgorithm<?> algorithm, Callable<T> c, Function<T, U> f) {
@@ -1410,20 +1474,22 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
   public Notification createNotification(
       final String bucket, final NotificationInfo notificationInfo) {
     final com.google.api.services.storage.model.Notification notificationPb =
-        notificationInfo.toPb();
+        codecs.notificationInfo().encode(notificationInfo);
     try {
-      return Notification.fromPb(
-          this,
-          runWithRetries(
-              new Callable<com.google.api.services.storage.model.Notification>() {
-                @Override
-                public com.google.api.services.storage.model.Notification call() {
-                  return storageRpc.createNotification(bucket, notificationPb);
-                }
-              },
-              getOptions().getRetrySettings(),
-              EXCEPTION_HANDLER,
-              getOptions().getClock()));
+      return codecs
+          .notificationInfo()
+          .decode(
+              runWithRetries(
+                  new Callable<com.google.api.services.storage.model.Notification>() {
+                    @Override
+                    public com.google.api.services.storage.model.Notification call() {
+                      return storageRpc.createNotification(bucket, notificationPb);
+                    }
+                  },
+                  getOptions().getRetrySettings(),
+                  EXCEPTION_HANDLER,
+                  getOptions().getClock()))
+          .asNotification(this);
     } catch (RetryHelperException e) {
       throw StorageException.translateAndThrow(e);
     }
@@ -1443,7 +1509,7 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
               getOptions().getRetrySettings(),
               EXCEPTION_HANDLER,
               getOptions().getClock());
-      return answer == null ? null : Notification.fromPb(this, answer);
+      return answer == null ? null : codecs.notificationInfo().decode(answer).asNotification(this);
     } catch (RetryHelperException e) {
       throw StorageException.translateAndThrow(e);
     }
@@ -1472,7 +1538,10 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
                 @Override
                 public Notification apply(
                     com.google.api.services.storage.model.Notification notificationPb) {
-                  return Notification.fromPb(getOptions().getService(), notificationPb);
+                  return codecs
+                      .notificationInfo()
+                      .decode(notificationPb)
+                      .asNotification(getOptions().getService());
                 }
               });
     } catch (RetryHelperException e) {
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java
new file mode 100644
index 00000000..fb932b07
--- /dev/null
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed 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 com.google.cloud.storage;
+
+import com.google.api.core.InternalApi;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import javax.annotation.Nullable;
+
+/**
+ * A collection of general utility functions providing convenience facilities.
+ *
+ * <p>Nothing in here should be Storage specific. Anything Storage specific should go in an
+ * appropriately named and scoped class.
+ */
+@InternalApi
+final class Utils {
+
+  private Utils() {}
+
+  /**
+   * If the value provided as {@code t} is non-null, consume it via {@code c}.
+   *
+   * <p>Helper method to allow for more terse expression of:
+   *
+   * <pre>{@code
+   * if (t != null) {
+   *   x.setT(t);
+   * }
+   * }</pre>
+   */
+  @InternalApi
+  static <T> void ifNonNull(@Nullable T t, Consumer<T> c) {
+    if (t != null) {
+      c.accept(t);
+    }
+  }
+
+  /**
+   * If the value provided as {@code t} is non-null, transform it using {@code map} and consume it
+   * via {@code c}.
+   *
+   * <p>Helper method to allow for more terse expression of:
+   *
+   * <pre>{@code
+   * if (t != null) {
+   *   x.setT(map.apply(t));
+   * }
+   * }</pre>
+   */
+  @InternalApi
+  static <T1, T2> void ifNonNull(@Nullable T1 t, Function<T1, T2> map, Consumer<T2> c) {
+    if (t != null) {
+      c.accept(map.apply(t));
+    }
+  }
+
+  /**
+   * Convenience method to "lift" a method reference to a {@link Function}.
+   *
+   * <p>While a method reference can be pass as an argument to a method which expects a {@code
+   * Function} it does not then allow calling {@link Function#andThen(Function) #andThen(Function)}.
+   * This method forces the method reference to be a {@code Function} thereby allowing {@code
+   * #andThen} composition.
+   */
+  @InternalApi
+  static <T1, T2> Function<T1, T2> lift(Function<T1, T2> f) {
+    return f;
+  }
+}


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/AclTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/AclTest.java
index e86c5c09..c011c285 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/AclTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/AclTest.java
@@ -29,6 +29,7 @@ import com.google.cloud.storage.Acl.Project.ProjectRole;
 import com.google.cloud.storage.Acl.RawEntity;
 import com.google.cloud.storage.Acl.Role;
 import com.google.cloud.storage.Acl.User;
+import com.google.cloud.storage.Conversions.Codec;
 import org.junit.Test;
 
 public class AclTest {
@@ -38,6 +39,14 @@ public class AclTest {
   private static final String ETAG = "etag";
   private static final String ID = "id";
   private static final Acl ACL = Acl.newBuilder(ENTITY, ROLE).setEtag(ETAG).setId(ID).build();
+  private static final Codec<Entity, String> CODEC_ENTITY = Conversions.apiary().entity();
+  private static final Codec<Acl, ObjectAccessControl> CODEC_ACL_OBJECT =
+      Conversions.apiary().objectAcl();
+  private static final Codec<Acl, BucketAccessControl> CODEC_ACL_BUCKET =
+      Conversions.apiary().bucketAcl();
+
+  static {
+  }
 
   @Test
   public void testBuilder() {
@@ -65,8 +74,8 @@ public class AclTest {
 
   @Test
   public void testToAndFromPb() {
-    assertEquals(ACL, Acl.fromPb(ACL.toBucketPb()));
-    assertEquals(ACL, Acl.fromPb(ACL.toObjectPb()));
+    assertEquals(ACL, CODEC_ACL_BUCKET.decode(CODEC_ACL_BUCKET.encode(ACL)));
+    assertEquals(ACL, CODEC_ACL_OBJECT.decode(CODEC_ACL_OBJECT.encode(ACL)));
   }
 
   @Test
@@ -74,8 +83,8 @@ public class AclTest {
     Domain acl = new Domain("d1");
     assertEquals("d1", acl.getDomain());
     assertEquals(Type.DOMAIN, acl.getType());
-    String pb = acl.toPb();
-    assertEquals(acl, Entity.fromPb(pb));
+    String pb = CODEC_ENTITY.encode(acl);
+    assertEquals(acl, CODEC_ENTITY.decode(pb));
   }
 
   @Test
@@ -83,8 +92,8 @@ public class AclTest {
     Group acl = new Group("g1");
     assertEquals("g1", acl.getEmail());
     assertEquals(Type.GROUP, acl.getType());
-    String pb = acl.toPb();
-    assertEquals(acl, Entity.fromPb(pb));
+    String pb = CODEC_ENTITY.encode(acl);
+    assertEquals(acl, CODEC_ENTITY.decode(pb));
   }
 
   @Test
@@ -92,8 +101,8 @@ public class AclTest {
     User acl = new User("u1");
     assertEquals("u1", acl.getEmail());
     assertEquals(Type.USER, acl.getType());
-    String pb = acl.toPb();
-    assertEquals(acl, Entity.fromPb(pb));
+    String pb = CODEC_ENTITY.encode(acl);
+    assertEquals(acl, CODEC_ENTITY.decode(pb));
   }
 
   @Test
@@ -102,8 +111,8 @@ public class AclTest {
     assertEquals(ProjectRole.VIEWERS, acl.getProjectRole());
     assertEquals("p1", acl.getProjectId());
     assertEquals(Type.PROJECT, acl.getType());
-    String pb = acl.toPb();
-    assertEquals(acl, Entity.fromPb(pb));
+    String pb = CODEC_ENTITY.encode(acl);
+    assertEquals(acl, CODEC_ENTITY.decode(pb));
   }
 
   @Test
@@ -111,8 +120,8 @@ public class AclTest {
     Entity acl = new RawEntity("bla");
     assertEquals("bla", acl.getValue());
     assertEquals(Type.UNKNOWN, acl.getType());
-    String pb = acl.toPb();
-    assertEquals(acl, Entity.fromPb(pb));
+    String pb = CODEC_ENTITY.encode(acl);
+    assertEquals(acl, CODEC_ENTITY.decode(pb));
   }
 
   @Test
@@ -120,9 +129,9 @@ public class AclTest {
     Acl acl = Acl.of(User.ofAllUsers(), Role.READER);
     assertEquals(User.ofAllUsers(), acl.getEntity());
     assertEquals(Role.READER, acl.getRole());
-    ObjectAccessControl objectPb = acl.toObjectPb();
-    assertEquals(acl, Acl.fromPb(objectPb));
-    BucketAccessControl bucketPb = acl.toBucketPb();
-    assertEquals(acl, Acl.fromPb(bucketPb));
+    ObjectAccessControl objectPb = CODEC_ACL_OBJECT.encode(acl);
+    assertEquals(acl, CODEC_ACL_OBJECT.decode(objectPb));
+    BucketAccessControl bucketPb = CODEC_ACL_BUCKET.encode(acl);
+    assertEquals(acl, CODEC_ACL_BUCKET.decode(bucketPb));
   }
 }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobIdTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobIdTest.java
index d90760a4..0c13fa42 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobIdTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobIdTest.java
@@ -53,6 +53,7 @@ public class BlobIdTest {
 
   @Test
   public void testToPbAndFromPb() {
-    compareBlobIds(BLOB, BlobId.fromPb(BLOB.toPb()));
+    compareBlobIds(
+        BLOB, Conversions.apiary().blobId().decode(Conversions.apiary().blobId().encode(BLOB)));
   }
 }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java
index d23e0dc7..d17cb865 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java
@@ -284,17 +284,24 @@ public class BlobInfoTest {
   @Test
   public void testToPbAndFromPb() {
     compareCustomerEncryptions(
-        CUSTOMER_ENCRYPTION, CustomerEncryption.fromPb(CUSTOMER_ENCRYPTION.toPb()));
-    compareBlobs(BLOB_INFO, BlobInfo.fromPb(BLOB_INFO.toPb()));
+        CUSTOMER_ENCRYPTION,
+        Conversions.apiary()
+            .customerEncryption()
+            .decode(Conversions.apiary().customerEncryption().encode(CUSTOMER_ENCRYPTION)));
+    compareBlobs(
+        BLOB_INFO,
+        Conversions.apiary().blobInfo().decode(Conversions.apiary().blobInfo().encode(BLOB_INFO)));
     BlobInfo blobInfo = BlobInfo.newBuilder(BlobId.of("b", "n")).build();
-    compareBlobs(blobInfo, BlobInfo.fromPb(blobInfo.toPb()));
+    compareBlobs(
+        blobInfo,
+        Conversions.apiary().blobInfo().decode(Conversions.apiary().blobInfo().encode(blobInfo)));
     StorageObject object =
         new StorageObject()
             .setName("n/")
             .setBucket("b")
             .setSize(BigInteger.ZERO)
             .set("isDirectory", true);
-    blobInfo = BlobInfo.fromPb(object);
+    blobInfo = Conversions.apiary().blobInfo().decode(object);
     assertEquals("b", blobInfo.getBucket());
     assertEquals("n/", blobInfo.getName());
     assertNull(blobInfo.getAcl());
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobReadChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobReadChannelTest.java
index 6eb2b8aa..4da5b3f7 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobReadChannelTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobReadChannelTest.java
@@ -89,7 +89,12 @@ public class BlobReadChannelTest {
     byte[] result = randomByteArray(DEFAULT_CHUNK_SIZE);
     ByteBuffer firstReadBuffer = ByteBuffer.allocate(42);
     ByteBuffer secondReadBuffer = ByteBuffer.allocate(42);
-    expect(storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 0, DEFAULT_CHUNK_SIZE))
+    expect(
+            storageRpcMock.read(
+                Conversions.apiary().blobId().encode(BLOB_ID),
+                EMPTY_RPC_OPTIONS,
+                0,
+                DEFAULT_CHUNK_SIZE))
         .andReturn(Tuple.of("etag", result));
     replay(storageRpcMock);
     reader.read(firstReadBuffer);
@@ -111,11 +116,19 @@ public class BlobReadChannelTest {
     byte[] secondResult = randomByteArray(DEFAULT_CHUNK_SIZE);
     ByteBuffer firstReadBuffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE);
     ByteBuffer secondReadBuffer = ByteBuffer.allocate(42);
-    expect(storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 0, DEFAULT_CHUNK_SIZE))
+    expect(
+            storageRpcMock.read(
+                Conversions.apiary().blobId().encode(BLOB_ID),
+                EMPTY_RPC_OPTIONS,
+                0,
+                DEFAULT_CHUNK_SIZE))
         .andReturn(Tuple.of("etag", firstResult));
     expect(
             storageRpcMock.read(
-                BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, DEFAULT_CHUNK_SIZE, CUSTOM_CHUNK_SIZE))
+                Conversions.apiary().blobId().encode(BLOB_ID),
+                EMPTY_RPC_OPTIONS,
+                DEFAULT_CHUNK_SIZE,
+                CUSTOM_CHUNK_SIZE))
         .andReturn(Tuple.of("etag", secondResult));
     replay(storageRpcMock);
     reader.read(firstReadBuffer);
@@ -130,7 +143,12 @@ public class BlobReadChannelTest {
     reader = new BlobReadChannel(options, BLOB_ID, EMPTY_RPC_OPTIONS);
     byte[] result = {};
     ByteBuffer readBuffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE);
-    expect(storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 0, DEFAULT_CHUNK_SIZE))
+    expect(
+            storageRpcMock.read(
+                Conversions.apiary().blobId().encode(BLOB_ID),
+                EMPTY_RPC_OPTIONS,
+                0,
+                DEFAULT_CHUNK_SIZE))
         .andReturn(Tuple.of("etag", result));
     replay(storageRpcMock);
     assertEquals(-1, reader.read(readBuffer));
@@ -142,7 +160,12 @@ public class BlobReadChannelTest {
     reader.seek(42);
     byte[] result = randomByteArray(DEFAULT_CHUNK_SIZE);
     ByteBuffer readBuffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE);
-    expect(storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 42, DEFAULT_CHUNK_SIZE))
+    expect(
+            storageRpcMock.read(
+                Conversions.apiary().blobId().encode(BLOB_ID),
+                EMPTY_RPC_OPTIONS,
+                42,
+                DEFAULT_CHUNK_SIZE))
         .andReturn(Tuple.of("etag", result));
     replay(storageRpcMock);
     reader.read(readBuffer);
@@ -180,11 +203,19 @@ public class BlobReadChannelTest {
     byte[] secondResult = randomByteArray(DEFAULT_CHUNK_SIZE);
     ByteBuffer firstReadBuffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE);
     ByteBuffer secondReadBuffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE);
-    expect(storageRpcMock.read(blobId.toPb(), EMPTY_RPC_OPTIONS, 0, DEFAULT_CHUNK_SIZE))
+    expect(
+            storageRpcMock.read(
+                Conversions.apiary().blobId().encode(blobId),
+                EMPTY_RPC_OPTIONS,
+                0,
+                DEFAULT_CHUNK_SIZE))
         .andReturn(Tuple.of("etag1", firstResult));
     expect(
             storageRpcMock.read(
-                blobId.toPb(), EMPTY_RPC_OPTIONS, DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_SIZE))
+                Conversions.apiary().blobId().encode(blobId),
+                EMPTY_RPC_OPTIONS,
+                DEFAULT_CHUNK_SIZE,
+                DEFAULT_CHUNK_SIZE))
         .andReturn(Tuple.of("etag2", secondResult));
     replay(storageRpcMock);
     reader.read(firstReadBuffer);
@@ -204,9 +235,19 @@ public class BlobReadChannelTest {
     byte[] secondResult = randomByteArray(DEFAULT_CHUNK_SIZE);
     ByteBuffer firstReadBuffer = ByteBuffer.allocate(42);
     ByteBuffer secondReadBuffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE);
-    expect(storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 0, DEFAULT_CHUNK_SIZE))
+    expect(
+            storageRpcMock.read(
+                Conversions.apiary().blobId().encode(BLOB_ID),
+                EMPTY_RPC_OPTIONS,
+                0,
+                DEFAULT_CHUNK_SIZE))
         .andReturn(Tuple.of("etag", firstResult));
-    expect(storageRpcMock.read(BLOB_ID.toPb(), EMPTY_RPC_OPTIONS, 42, DEFAULT_CHUNK_SIZE))
+    expect(
+            storageRpcMock.read(
+                Conversions.apiary().blobId().encode(BLOB_ID),
+                EMPTY_RPC_OPTIONS,
+                42,
+                DEFAULT_CHUNK_SIZE))
         .andReturn(Tuple.of("etag", secondResult));
     replay(storageRpcMock);
     reader = new BlobReadChannel(options, BLOB_ID, EMPTY_RPC_OPTIONS);
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java
index 000a925c..e2fd5cf5 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java
@@ -107,7 +107,10 @@ public class BlobWriteChannelTest {
 
   @Test
   public void testCreate() {
-    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS))
+        .andReturn(UPLOAD_ID);
     replay(storageRpcMock);
     writer = newWriter();
     assertTrue(writer.isOpen());
@@ -116,9 +119,15 @@ public class BlobWriteChannelTest {
 
   @Test
   public void testCreateRetryableError() {
-    expect(storageRpcMock.open(BLOB_INFO_WITH_GENERATION.toPb(), RPC_OPTIONS_GENERATION))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION),
+                RPC_OPTIONS_GENERATION))
         .andThrow(socketClosedException);
-    expect(storageRpcMock.open(BLOB_INFO_WITH_GENERATION.toPb(), RPC_OPTIONS_GENERATION))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION),
+                RPC_OPTIONS_GENERATION))
         .andReturn(UPLOAD_ID);
     replay(storageRpcMock);
     writer = newWriter(true);
@@ -128,7 +137,9 @@ public class BlobWriteChannelTest {
 
   @Test
   public void testCreateNonRetryableError() {
-    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS))
         .andThrow(new RuntimeException());
     replay(storageRpcMock);
     try {
@@ -141,7 +152,10 @@ public class BlobWriteChannelTest {
 
   @Test
   public void testWriteWithoutFlush() throws Exception {
-    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS))
+        .andReturn(UPLOAD_ID);
     replay(storageRpcMock);
     writer = newWriter();
     assertEquals(MIN_CHUNK_SIZE, writer.write(ByteBuffer.allocate(MIN_CHUNK_SIZE)));
@@ -151,7 +165,10 @@ public class BlobWriteChannelTest {
   public void testWriteWithFlushRetryChunk() throws Exception {
     ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
-    expect(storageRpcMock.open(BLOB_INFO_WITH_GENERATION.toPb(), RPC_OPTIONS_GENERATION))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION),
+                RPC_OPTIONS_GENERATION))
         .andReturn(UPLOAD_ID);
     expect(
             storageRpcMock.writeWithResponse(
@@ -185,7 +202,10 @@ public class BlobWriteChannelTest {
   public void testWriteWithRetryFullChunk() throws Exception {
     ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
-    expect(storageRpcMock.open(BLOB_INFO_WITH_GENERATION.toPb(), RPC_OPTIONS_GENERATION))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION),
+                RPC_OPTIONS_GENERATION))
         .andReturn(UPLOAD_ID);
     expect(
             storageRpcMock.writeWithResponse(
@@ -209,7 +229,7 @@ public class BlobWriteChannelTest {
                 eq((long) MIN_CHUNK_SIZE),
                 eq(0),
                 eq(true)))
-        .andReturn(BLOB_INFO.toPb());
+        .andReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO));
     replay(storageRpcMock);
     writer = newWriter(true);
     writer.setChunkSize(MIN_CHUNK_SIZE);
@@ -224,7 +244,10 @@ public class BlobWriteChannelTest {
   public void testWriteWithRemoteProgressMade() throws Exception {
     ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
-    expect(storageRpcMock.open(BLOB_INFO_WITH_GENERATION.toPb(), RPC_OPTIONS_GENERATION))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION),
+                RPC_OPTIONS_GENERATION))
         .andReturn(UPLOAD_ID);
     expect(
             storageRpcMock.writeWithResponse(
@@ -259,7 +282,10 @@ public class BlobWriteChannelTest {
   public void testWriteWithDriftRetryCase4() throws Exception {
     ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
-    expect(storageRpcMock.open(BLOB_INFO_WITH_GENERATION.toPb(), RPC_OPTIONS_GENERATION))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION),
+                RPC_OPTIONS_GENERATION))
         .andReturn(UPLOAD_ID);
     expect(
             storageRpcMock.writeWithResponse(
@@ -297,7 +323,10 @@ public class BlobWriteChannelTest {
   public void testWriteWithUnreachableRemoteOffset() throws Exception {
     ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
-    expect(storageRpcMock.open(BLOB_INFO_WITH_GENERATION.toPb(), RPC_OPTIONS_GENERATION))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION),
+                RPC_OPTIONS_GENERATION))
         .andReturn(UPLOAD_ID);
     expect(
             storageRpcMock.writeWithResponse(
@@ -327,7 +356,10 @@ public class BlobWriteChannelTest {
   public void testWriteWithRetryAndObjectMetadata() throws Exception {
     ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
-    expect(storageRpcMock.open(BLOB_INFO_WITH_GENERATION.toPb(), RPC_OPTIONS_GENERATION))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION),
+                RPC_OPTIONS_GENERATION))
         .andReturn(UPLOAD_ID);
     expect(
             storageRpcMock.writeWithResponse(
@@ -354,7 +386,11 @@ public class BlobWriteChannelTest {
         .andThrow(socketClosedException);
     expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(-1L);
     expect(storageRpcMock.queryCompletedResumableUpload(eq(UPLOAD_ID), eq((long) MIN_CHUNK_SIZE)))
-        .andReturn(BLOB_INFO.toPb().setSize(BigInteger.valueOf(MIN_CHUNK_SIZE)));
+        .andReturn(
+            Conversions.apiary()
+                .blobInfo()
+                .encode(BLOB_INFO)
+                .setSize(BigInteger.valueOf(MIN_CHUNK_SIZE)));
     replay(storageRpcMock);
     writer = newWriter(true);
     writer.setChunkSize(MIN_CHUNK_SIZE);
@@ -369,7 +405,10 @@ public class BlobWriteChannelTest {
   public void testWriteWithUploadCompletedByAnotherClient() throws Exception {
     ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
-    expect(storageRpcMock.open(BLOB_INFO_WITH_GENERATION.toPb(), RPC_OPTIONS_GENERATION))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION),
+                RPC_OPTIONS_GENERATION))
         .andReturn(UPLOAD_ID);
     expect(
             storageRpcMock.writeWithResponse(
@@ -411,7 +450,10 @@ public class BlobWriteChannelTest {
   public void testWriteWithLocalOffsetGoingBeyondRemoteOffset() throws Exception {
     ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
-    expect(storageRpcMock.open(BLOB_INFO_WITH_GENERATION.toPb(), RPC_OPTIONS_GENERATION))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION),
+                RPC_OPTIONS_GENERATION))
         .andReturn(UPLOAD_ID);
     expect(
             storageRpcMock.writeWithResponse(
@@ -450,7 +492,10 @@ public class BlobWriteChannelTest {
   public void testGetCurrentUploadOffset() throws Exception {
     ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
-    expect(storageRpcMock.open(BLOB_INFO_WITH_GENERATION.toPb(), RPC_OPTIONS_GENERATION))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION),
+                RPC_OPTIONS_GENERATION))
         .andReturn(UPLOAD_ID);
     expect(
             storageRpcMock.writeWithResponse(
@@ -480,7 +525,7 @@ public class BlobWriteChannelTest {
                 eq((long) MIN_CHUNK_SIZE),
                 eq(0),
                 eq(true)))
-        .andReturn(BLOB_INFO.toPb());
+        .andReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO));
     replay(storageRpcMock);
     writer = newWriter(true);
     writer.setChunkSize(MIN_CHUNK_SIZE);
@@ -495,7 +540,10 @@ public class BlobWriteChannelTest {
   public void testWriteWithLastFlushRetryChunkButCompleted() throws Exception {
     ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
-    expect(storageRpcMock.open(BLOB_INFO_WITH_GENERATION.toPb(), RPC_OPTIONS_GENERATION))
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION),
+                RPC_OPTIONS_GENERATION))
         .andReturn(UPLOAD_ID);
     expect(
             storageRpcMock.writeWithResponse(
@@ -508,7 +556,11 @@ public class BlobWriteChannelTest {
         .andThrow(socketClosedException);
     expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(-1L);
     expect(storageRpcMock.queryCompletedResumableUpload(eq(UPLOAD_ID), eq((long) MIN_CHUNK_SIZE)))
-        .andReturn(BLOB_INFO.toPb().setSize(BigInteger.valueOf(MIN_CHUNK_SIZE)));
+        .andReturn(
+            Conversions.apiary()
+                .blobInfo()
+                .encode(BLOB_INFO)
+                .setSize(BigInteger.valueOf(MIN_CHUNK_SIZE)));
     replay(storageRpcMock);
     writer = newWriter(true);
     assertEquals(MIN_CHUNK_SIZE, writer.write(buffer));
@@ -523,7 +575,10 @@ public class BlobWriteChannelTest {
 
   @Test
   public void testWriteWithFlush() throws Exception {
-    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS))
+        .andReturn(UPLOAD_ID);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
     expect(
             storageRpcMock.writeWithResponse(
@@ -545,7 +600,10 @@ public class BlobWriteChannelTest {
 
   @Test
   public void testWritesAndFlush() throws Exception {
-    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS))
+        .andReturn(UPLOAD_ID);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
     expect(
             storageRpcMock.writeWithResponse(
@@ -574,7 +632,10 @@ public class BlobWriteChannelTest {
 
   @Test
   public void testCloseWithoutFlush() throws Exception {
-    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS))
+        .andReturn(UPLOAD_ID);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
     expect(
             storageRpcMock.writeWithResponse(
@@ -591,7 +652,10 @@ public class BlobWriteChannelTest {
 
   @Test
   public void testCloseWithFlush() throws Exception {
-    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS))
+        .andReturn(UPLOAD_ID);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
     ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
     expect(
@@ -616,7 +680,10 @@ public class BlobWriteChannelTest {
 
   @Test
   public void testWriteClosed() throws Exception {
-    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS))
+        .andReturn(UPLOAD_ID);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
     expect(
             storageRpcMock.writeWithResponse(
@@ -636,7 +703,10 @@ public class BlobWriteChannelTest {
 
   @Test
   public void testSaveAndRestore() throws Exception {
-    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS))
+        .andReturn(UPLOAD_ID);
     Capture<byte[]> capturedBuffer = Capture.newInstance(CaptureType.ALL);
     Capture<Long> capturedPosition = Capture.newInstance(CaptureType.ALL);
     expect(
@@ -665,7 +735,10 @@ public class BlobWriteChannelTest {
 
   @Test
   public void testSaveAndRestoreClosed() throws Exception {
-    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS))
+        .andReturn(UPLOAD_ID);
     Capture<byte[]> capturedBuffer = Capture.newInstance();
     expect(
             storageRpcMock.writeWithResponse(
@@ -689,7 +762,11 @@ public class BlobWriteChannelTest {
 
   @Test
   public void testStateEquals() {
-    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID).times(2);
+    expect(
+            storageRpcMock.open(
+                Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS))
+        .andReturn(UPLOAD_ID)
+        .times(2);
     replay(storageRpcMock);
     writer = newWriter();
     // avoid closing when you don't want partial writes to GCS upon failure
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java
index 2041de55..6597303b 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java
@@ -243,14 +243,22 @@ public class BucketInfoTest {
   @Test
   @SuppressWarnings({"unchecked", "deprecation"})
   public void testToPbAndFromPb() {
-    compareBuckets(BUCKET_INFO, BucketInfo.fromPb(BUCKET_INFO.toPb()));
+    compareBuckets(
+        BUCKET_INFO,
+        Conversions.apiary()
+            .bucketInfo()
+            .decode(Conversions.apiary().bucketInfo().encode(BUCKET_INFO)));
     BucketInfo bucketInfo =
         BucketInfo.newBuilder("b")
             .setDeleteRules(DELETE_RULES)
             .setLifecycleRules(LIFECYCLE_RULES)
             .setLogging(LOGGING)
             .build();
-    compareBuckets(bucketInfo, BucketInfo.fromPb(bucketInfo.toPb()));
+    compareBuckets(
+        bucketInfo,
+        Conversions.apiary()
+            .bucketInfo()
+            .decode(Conversions.apiary().bucketInfo().encode(bucketInfo)));
   }
 
   @SuppressWarnings({"unchecked", "deprecation"})
@@ -312,34 +320,43 @@ public class BucketInfoTest {
     ImmutableList<DeleteRule> rules =
         ImmutableList.of(ageRule, createBeforeRule, versionsRule, isLiveRule, rawRule);
     for (DeleteRule delRule : rules) {
-      assertEquals(delRule, DeleteRule.fromPb(delRule.toPb()));
+      assertEquals(
+          delRule,
+          Conversions.apiary()
+              .deleteRule()
+              .decode(Conversions.apiary().deleteRule().encode(delRule)));
     }
     Rule unsupportedRule =
         new Rule().setAction(new Rule.Action().setType("This action doesn't exist"));
-    DeleteRule.fromPb(
-        unsupportedRule); // if this doesn't throw an exception, unsupported rules work
+    Conversions.apiary()
+        .deleteRule()
+        .decode(unsupportedRule); // if this doesn't throw an exception, unsupported rules work
   }
 
   @Test
   public void testLifecycleRules() {
     Rule deleteLifecycleRule =
-        new LifecycleRule(
-                LifecycleAction.newDeleteAction(),
-                LifecycleCondition.newBuilder().setAge(10).build())
-            .toPb();
+        Conversions.apiary()
+            .lifecycleRule()
+            .encode(
+                new LifecycleRule(
+                    LifecycleAction.newDeleteAction(),
+                    LifecycleCondition.newBuilder().setAge(10).build()));
 
     assertEquals(
         LifecycleRule.DeleteLifecycleAction.TYPE, deleteLifecycleRule.getAction().getType());
     assertEquals(10, deleteLifecycleRule.getCondition().getAge().intValue());
 
     Rule setStorageClassLifecycleRule =
-        new LifecycleRule(
-                LifecycleAction.newSetStorageClassAction(StorageClass.COLDLINE),
-                LifecycleCondition.newBuilder()
-                    .setIsLive(true)
-                    .setNumberOfNewerVersions(10)
-                    .build())
-            .toPb();
+        Conversions.apiary()
+            .lifecycleRule()
+            .encode(
+                new LifecycleRule(
+                    LifecycleAction.newSetStorageClassAction(StorageClass.COLDLINE),
+                    LifecycleCondition.newBuilder()
+                        .setIsLive(true)
+                        .setNumberOfNewerVersions(10)
+                        .build()));
 
     assertEquals(
         StorageClass.COLDLINE.toString(),
@@ -348,17 +365,19 @@ public class BucketInfoTest {
     assertEquals(10, setStorageClassLifecycleRule.getCondition().getNumNewerVersions().intValue());
 
     Rule lifecycleRule =
-        new LifecycleRule(
-                LifecycleAction.newSetStorageClassAction(StorageClass.COLDLINE),
-                LifecycleCondition.newBuilder()
-                    .setIsLive(true)
-                    .setNumberOfNewerVersions(10)
-                    .setDaysSinceNoncurrentTime(30)
-                    .setNoncurrentTimeBefore(new DateTime(System.currentTimeMillis()))
-                    .setCustomTimeBefore(new DateTime(System.currentTimeMillis()))
-                    .setDaysSinceCustomTime(30)
-                    .build())
-            .toPb();
+        Conversions.apiary()
+            .lifecycleRule()
+            .encode(
+                new LifecycleRule(
+                    LifecycleAction.newSetStorageClassAction(StorageClass.COLDLINE),
+                    LifecycleCondition.newBuilder()
+                        .setIsLive(true)
+                        .setNumberOfNewerVersions(10)
+                        .setDaysSinceNoncurrentTime(30)
+                        .setNoncurrentTimeBefore(new DateTime(System.currentTimeMillis()))
+                        .setCustomTimeBefore(new DateTime(System.currentTimeMillis()))
+                        .setDaysSinceCustomTime(30)
+                        .build()));
     assertEquals(StorageClass.COLDLINE.toString(), lifecycleRule.getAction().getStorageClass());
     assertTrue(lifecycleRule.getCondition().getIsLive());
     assertEquals(10, lifecycleRule.getCondition().getNumNewerVersions().intValue());
@@ -369,26 +388,32 @@ public class BucketInfoTest {
     assertNotNull(lifecycleRule.getCondition().getCustomTimeBefore());
 
     Rule unsupportedRule =
-        new LifecycleRule(
-                LifecycleAction.newLifecycleAction("This action type doesn't exist"),
-                LifecycleCondition.newBuilder().setAge(10).build())
-            .toPb();
+        Conversions.apiary()
+            .lifecycleRule()
+            .encode(
+                new LifecycleRule(
+                    LifecycleAction.newLifecycleAction("This action type doesn't exist"),
+                    LifecycleCondition.newBuilder().setAge(10).build()));
     unsupportedRule.setAction(
         unsupportedRule.getAction().setType("This action type also doesn't exist"));
 
-    LifecycleRule.fromPb(
-        unsupportedRule); // If this doesn't throw an exception, unsupported rules are working
+    Conversions.apiary()
+        .lifecycleRule()
+        .decode(
+            unsupportedRule); // If this doesn't throw an exception, unsupported rules are working
   }
 
   @Test
   public void testIamConfiguration() {
     Bucket.IamConfiguration iamConfiguration =
-        BucketInfo.IamConfiguration.newBuilder()
-            .setIsUniformBucketLevelAccessEnabled(true)
-            .setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
-            .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED)
-            .build()
-            .toPb();
+        Conversions.apiary()
+            .iamConfiguration()
+            .encode(
+                IamConfiguration.newBuilder()
+                    .setIsUniformBucketLevelAccessEnabled(true)
+                    .setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
+                    .setPublicAccessPrevention(PublicAccessPrevention.ENFORCED)
+                    .build());
 
     assertEquals(Boolean.TRUE, iamConfiguration.getUniformBucketLevelAccess().getEnabled());
     assertNotNull(iamConfiguration.getUniformBucketLevelAccess().getLockedTime());
@@ -404,12 +429,14 @@ public class BucketInfoTest {
         JacksonFactory.getDefaultInstance().createJsonGenerator(stringWriter);
 
     jsonGenerator.serialize(
-        BucketInfo.IamConfiguration.newBuilder()
-            .setIsUniformBucketLevelAccessEnabled(true)
-            .setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
-            .setPublicAccessPrevention(PublicAccessPrevention.UNKNOWN)
-            .build()
-            .toPb());
+        Conversions.apiary()
+            .iamConfiguration()
+            .encode(
+                IamConfiguration.newBuilder()
+                    .setIsUniformBucketLevelAccessEnabled(true)
+                    .setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
+                    .setPublicAccessPrevention(PublicAccessPrevention.UNKNOWN)
+                    .build()));
     jsonGenerator.flush();
 
     assertFalse(stringWriter.getBuffer().toString().contains("publicAccessPrevention"));
@@ -422,7 +449,7 @@ public class BucketInfoTest {
         new Bucket.IamConfiguration.UniformBucketLevelAccess();
     iamConfiguration.setUniformBucketLevelAccess(uniformBucketLevelAccess);
     iamConfiguration.setPublicAccessPrevention("random-string");
-    IamConfiguration fromPb = IamConfiguration.fromPb(iamConfiguration);
+    IamConfiguration fromPb = Conversions.apiary().iamConfiguration().decode(iamConfiguration);
 
     assertEquals(PublicAccessPrevention.UNKNOWN, fromPb.getPublicAccessPrevention());
   }
@@ -430,44 +457,52 @@ public class BucketInfoTest {
   @Test
   public void testLogging() {
     Bucket.Logging logging =
-        BucketInfo.Logging.newBuilder()
-            .setLogBucket("test-bucket")
-            .setLogObjectPrefix("test-")
-            .build()
-            .toPb();
+        Conversions.apiary()
+            .logging()
+            .encode(
+                BucketInfo.Logging.newBuilder()
+                    .setLogBucket("test-bucket")
+                    .setLogObjectPrefix("test-")
+                    .build());
     assertEquals("test-bucket", logging.getLogBucket());
     assertEquals("test-", logging.getLogObjectPrefix());
   }
 
   @Test
   public void testRuleMappingIsCorrect_noMutations() {
-    Bucket bucket = bi().build().toPb();
+    Bucket bucket = Conversions.apiary().bucketInfo().encode(bi().build());
     assertNull(bucket.getLifecycle());
   }
 
   @Test
   public void testRuleMappingIsCorrect_deleteLifecycleRules() {
-    Bucket bucket = bi().deleteLifecycleRules().build().toPb();
+    Bucket bucket = Conversions.apiary().bucketInfo().encode(bi().deleteLifecycleRules().build());
     assertEquals(EMPTY_LIFECYCLE, bucket.getLifecycle());
   }
 
   @Test
   @SuppressWarnings({"deprecation"})
   public void testRuleMappingIsCorrect_setDeleteRules_null() {
-    Bucket bucket = bi().setDeleteRules(null).build().toPb();
+    Bucket bucket = Conversions.apiary().bucketInfo().encode(bi().setDeleteRules(null).build());
     assertNull(bucket.getLifecycle());
   }
 
   @Test
   @SuppressWarnings({"deprecation"})
   public void testRuleMappingIsCorrect_setDeleteRules_empty() {
-    Bucket bucket = bi().setDeleteRules(Collections.<DeleteRule>emptyList()).build().toPb();
+    Bucket bucket =
+        Conversions.apiary()
+            .bucketInfo()
+            .encode(bi().setDeleteRules(Collections.<DeleteRule>emptyList()).build());
     assertEquals(EMPTY_LIFECYCLE, bucket.getLifecycle());
   }
 
   @Test
   public void testRuleMappingIsCorrect_setLifecycleRules_empty() {
-    Bucket bucket = bi().setLifecycleRules(Collections.<LifecycleRule>emptyList()).build().toPb();
+    Bucket bucket =
+        Conversions.apiary()
+            .bucketInfo()
+            .encode(bi().setLifecycleRules(Collections.<LifecycleRule>emptyList()).build());
     assertEquals(EMPTY_LIFECYCLE, bucket.getLifecycle());
   }
 
@@ -476,8 +511,11 @@ public class BucketInfoTest {
     LifecycleRule lifecycleRule =
         new LifecycleRule(
             LifecycleAction.newDeleteAction(), LifecycleCondition.newBuilder().setAge(10).build());
-    Rule lifecycleDeleteAfter10 = lifecycleRule.toPb();
-    Bucket bucket = bi().setLifecycleRules(ImmutableList.of(lifecycleRule)).build().toPb();
+    Rule lifecycleDeleteAfter10 = Conversions.apiary().lifecycleRule().encode(lifecycleRule);
+    Bucket bucket =
+        Conversions.apiary()
+            .bucketInfo()
+            .encode(bi().setLifecycleRules(ImmutableList.of(lifecycleRule)).build());
     assertEquals(lifecycle(lifecycleDeleteAfter10), bucket.getLifecycle());
   }
 
@@ -485,8 +523,11 @@ public class BucketInfoTest {
   @SuppressWarnings({"deprecation"})
   public void testRuleMappingIsCorrect_setDeleteRules_nonEmpty() {
     DeleteRule deleteRule = DELETE_RULES.get(0);
-    Rule deleteRuleAge5 = deleteRule.toPb();
-    Bucket bucket = bi().setDeleteRules(ImmutableList.of(deleteRule)).build().toPb();
+    Rule deleteRuleAge5 = Conversions.apiary().deleteRule().encode(deleteRule);
+    Bucket bucket =
+        Conversions.apiary()
+            .bucketInfo()
+            .encode(bi().setDeleteRules(ImmutableList.of(deleteRule)).build());
     assertEquals(lifecycle(deleteRuleAge5), bucket.getLifecycle());
   }
 
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/CopyWriterTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/CopyWriterTest.java
index a81f6680..718b60b9 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/CopyWriterTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/CopyWriterTest.java
@@ -53,18 +53,40 @@ public class CopyWriterTest {
   private static final Map<StorageRpc.Option, ?> EMPTY_OPTIONS = ImmutableMap.of();
   private static final RewriteRequest REQUEST_WITH_OBJECT =
       new StorageRpc.RewriteRequest(
-          BLOB_ID.toPb(), EMPTY_OPTIONS, true, BLOB_INFO.toPb(), EMPTY_OPTIONS, null);
+          Conversions.apiary().blobId().encode(BLOB_ID),
+          EMPTY_OPTIONS,
+          true,
+          Conversions.apiary().blobInfo().encode(BLOB_INFO),
+          EMPTY_OPTIONS,
+          null);
   private static final RewriteRequest REQUEST_WITHOUT_OBJECT =
       new StorageRpc.RewriteRequest(
-          BLOB_ID.toPb(), EMPTY_OPTIONS, false, BLOB_INFO.toPb(), EMPTY_OPTIONS, null);
+          Conversions.apiary().blobId().encode(BLOB_ID),
+          EMPTY_OPTIONS,
+          false,
+          Conversions.apiary().blobInfo().encode(BLOB_INFO),
+          EMPTY_OPTIONS,
+          null);
   private static final RewriteResponse RESPONSE_WITH_OBJECT =
       new RewriteResponse(REQUEST_WITH_OBJECT, null, 42L, false, "token", 21L);
   private static final RewriteResponse RESPONSE_WITHOUT_OBJECT =
       new RewriteResponse(REQUEST_WITHOUT_OBJECT, null, 42L, false, "token", 21L);
   private static final RewriteResponse RESPONSE_WITH_OBJECT_DONE =
-      new RewriteResponse(REQUEST_WITH_OBJECT, RESULT_INFO.toPb(), 42L, true, "token", 42L);
+      new RewriteResponse(
+          REQUEST_WITH_OBJECT,
+          Conversions.apiary().blobInfo().encode(RESULT_INFO),
+          42L,
+          true,
+          "token",
+          42L);
   private static final RewriteResponse RESPONSE_WITHOUT_OBJECT_DONE =
-      new RewriteResponse(REQUEST_WITHOUT_OBJECT, RESULT_INFO.toPb(), 42L, true, "token", 42L);
+      new RewriteResponse(
+          REQUEST_WITHOUT_OBJECT,
+          Conversions.apiary().blobInfo().encode(RESULT_INFO),
+          42L,
+          true,
+          "token",
+          42L);
 
   private StorageOptions options;
   private StorageRpcFactory rpcFactoryMock;
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationInfoTest.java
index fd50120a..c3a000fd 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationInfoTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationInfoTest.java
@@ -90,10 +90,17 @@ public class NotificationInfoTest {
   @Test
   public void testToPbAndFromPb() {
     compareBucketsNotification(
-        NOTIFICATION_INFO, NotificationInfo.fromPb(NOTIFICATION_INFO.toPb()));
+        NOTIFICATION_INFO,
+        Conversions.apiary()
+            .notificationInfo()
+            .decode(Conversions.apiary().notificationInfo().encode(NOTIFICATION_INFO)));
     NotificationInfo notificationInfo =
         NotificationInfo.of(TOPIC).toBuilder().setPayloadFormat(PayloadFormat.NONE).build();
-    compareBucketsNotification(notificationInfo, Notification.fromPb(notificationInfo.toPb()));
+    compareBucketsNotification(
+        notificationInfo,
+        Conversions.apiary()
+            .notificationInfo()
+            .decode(Conversions.apiary().notificationInfo().encode(notificationInfo)));
   }
 
   private void compareBucketsNotification(NotificationInfo expected, NotificationInfo actual) {
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationTest.java
index 2f1f7eb3..85121fbe 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/NotificationTest.java
@@ -108,7 +108,11 @@ public class NotificationTest {
     expect(storage.getOptions()).andReturn(mockOptions).times(1);
     replay(storage);
     compareBucketNotification(
-        NOTIFICATION_INFO, Notification.fromPb(storage, NOTIFICATION_INFO.toPb()));
+        NOTIFICATION_INFO,
+        Conversions.apiary()
+            .notificationInfo()
+            .decode(Conversions.apiary().notificationInfo().encode(NOTIFICATION_INFO))
+            .asNotification(storage));
   }
 
   private void compareBucketNotification(NotificationInfo expected, NotificationInfo actual) {
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/PackagePrivateMethodWorkarounds.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/PackagePrivateMethodWorkarounds.java
index 738832ef..85c4f4aa 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/PackagePrivateMethodWorkarounds.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/PackagePrivateMethodWorkarounds.java
@@ -34,12 +34,22 @@ public final class PackagePrivateMethodWorkarounds {
   private PackagePrivateMethodWorkarounds() {}
 
   public static Bucket bucketCopyWithStorage(Bucket b, Storage s) {
-    BucketInfo.BuilderImpl builder = (BuilderImpl) BucketInfo.fromPb(b.toPb()).toBuilder();
+    BucketInfo.BuilderImpl builder =
+        (BuilderImpl)
+            Conversions.apiary()
+                .bucketInfo()
+                .decode(Conversions.apiary().bucketInfo().encode(b))
+                .toBuilder();
     return new Bucket(s, builder);
   }
 
   public static Blob blobCopyWithStorage(Blob b, Storage s) {
-    BlobInfo.BuilderImpl builder = (BlobInfo.BuilderImpl) BlobInfo.fromPb(b.toPb()).toBuilder();
+    BlobInfo.BuilderImpl builder =
+        (BlobInfo.BuilderImpl)
+            Conversions.apiary()
+                .blobInfo()
+                .decode(Conversions.apiary().blobInfo().encode(b))
+                .toBuilder();
     return new Blob(s, builder);
   }
 
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ServiceAccountTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ServiceAccountTest.java
index e0f377b1..225ea26e 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ServiceAccountTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ServiceAccountTest.java
@@ -31,7 +31,11 @@ public class ServiceAccountTest {
 
   @Test
   public void testToAndFromPb() {
-    compareServiceAccount(SERVICE_ACCOUNT, ServiceAccount.fromPb(SERVICE_ACCOUNT.toPb()));
+    compareServiceAccount(
+        SERVICE_ACCOUNT,
+        Conversions.apiary()
+            .serviceAccount()
+            .decode(Conversions.apiary().serviceAccount().encode(SERVICE_ACCOUNT)));
   }
 
   public void compareServiceAccount(ServiceAccount expected, ServiceAccount value) {
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageBatchTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageBatchTest.java
index 978cffe0..b0bf671a 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageBatchTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageBatchTest.java
@@ -94,7 +94,7 @@ public class StorageBatchTest {
     EasyMock.reset(batchMock);
     Capture<RpcBatch.Callback<Void>> callback = Capture.newInstance();
     batchMock.addDelete(
-        EasyMock.eq(BLOB_INFO.toPb()),
+        EasyMock.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO)),
         EasyMock.capture(callback),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     EasyMock.replay(batchMock);
@@ -124,7 +124,7 @@ public class StorageBatchTest {
     Capture<RpcBatch.Callback<Void>> callback = Capture.newInstance();
     Capture<Map<StorageRpc.Option, Object>> capturedOptions = Capture.newInstance();
     batchMock.addDelete(
-        EasyMock.eq(BLOB_INFO.toPb()),
+        EasyMock.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO)),
         EasyMock.capture(callback),
         EasyMock.capture(capturedOptions));
     EasyMock.replay(batchMock);
@@ -144,7 +144,7 @@ public class StorageBatchTest {
     EasyMock.reset(batchMock);
     Capture<RpcBatch.Callback<StorageObject>> callback = Capture.newInstance();
     batchMock.addPatch(
-        EasyMock.eq(BLOB_INFO.toPb()),
+        EasyMock.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO)),
         EasyMock.capture(callback),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     EasyMock.replay(batchMock);
@@ -178,7 +178,7 @@ public class StorageBatchTest {
     Capture<RpcBatch.Callback<StorageObject>> callback = Capture.newInstance();
     Capture<Map<StorageRpc.Option, Object>> capturedOptions = Capture.newInstance();
     batchMock.addPatch(
-        EasyMock.eq(BLOB_INFO_COMPLETE.toPb()),
+        EasyMock.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO_COMPLETE)),
         EasyMock.capture(callback),
         EasyMock.capture(capturedOptions));
     EasyMock.replay(batchMock, storage, optionsMock);
@@ -189,7 +189,7 @@ public class StorageBatchTest {
     assertEquals(42L, capturedOptions.getValue().get(BLOB_TARGET_OPTIONS[0].getRpcOption()));
     assertEquals(42L, capturedOptions.getValue().get(BLOB_TARGET_OPTIONS[1].getRpcOption()));
     RpcBatch.Callback<StorageObject> capturedCallback = callback.getValue();
-    capturedCallback.onSuccess(BLOB_INFO.toPb());
+    capturedCallback.onSuccess(Conversions.apiary().blobInfo().encode(BLOB_INFO));
     assertEquals(new Blob(storage, new Blob.BuilderImpl(BLOB_INFO)), batchResult.get());
   }
 
@@ -198,7 +198,7 @@ public class StorageBatchTest {
     EasyMock.reset(batchMock);
     Capture<RpcBatch.Callback<StorageObject>> callback = Capture.newInstance();
     batchMock.addGet(
-        EasyMock.eq(BLOB_INFO.toPb()),
+        EasyMock.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO)),
         EasyMock.capture(callback),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     EasyMock.replay(batchMock);
@@ -232,7 +232,7 @@ public class StorageBatchTest {
     Capture<RpcBatch.Callback<StorageObject>> callback = Capture.newInstance();
     Capture<Map<StorageRpc.Option, Object>> capturedOptions = Capture.newInstance();
     batchMock.addGet(
-        EasyMock.eq(BLOB_INFO.toPb()),
+        EasyMock.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO)),
         EasyMock.capture(callback),
         EasyMock.capture(capturedOptions));
     EasyMock.replay(storage, batchMock, optionsMock);
@@ -243,7 +243,7 @@ public class StorageBatchTest {
       assertEquals(option.getValue(), capturedOptions.getValue().get(option.getRpcOption()));
     }
     RpcBatch.Callback<StorageObject> capturedCallback = callback.getValue();
-    capturedCallback.onSuccess(BLOB_INFO.toPb());
+    capturedCallback.onSuccess(Conversions.apiary().blobInfo().encode(BLOB_INFO));
     assertEquals(new Blob(storage, new Blob.BuilderImpl(BLOB_INFO)), batchResult.get());
   }
 }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java
index e69e3fe0..edd060ee 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java
@@ -475,10 +475,10 @@ public class StorageImplMockitoTest {
 
   @Test
   public void testCreateBucket() {
-    doReturn(BUCKET_INFO1.toPb())
+    doReturn(Conversions.apiary().bucketInfo().encode(BUCKET_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .create(BUCKET_INFO1.toPb(), EMPTY_RPC_OPTIONS);
+        .create(Conversions.apiary().bucketInfo().encode(BUCKET_INFO1), EMPTY_RPC_OPTIONS);
     initializeService();
     Bucket bucket = storage.create(BUCKET_INFO1);
     assertEquals(expectedBucket1, bucket);
@@ -486,10 +486,10 @@ public class StorageImplMockitoTest {
 
   @Test
   public void testCreateBucketWithOptions() {
-    doReturn(BUCKET_INFO1.toPb())
+    doReturn(Conversions.apiary().bucketInfo().encode(BUCKET_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .create(BUCKET_INFO1.toPb(), BUCKET_TARGET_OPTIONS);
+        .create(Conversions.apiary().bucketInfo().encode(BUCKET_INFO1), BUCKET_TARGET_OPTIONS);
     initializeService();
     Bucket bucket =
         storage.create(BUCKET_INFO1, BUCKET_TARGET_METAGENERATION, BUCKET_TARGET_PREDEFINED_ACL);
@@ -498,7 +498,9 @@ public class StorageImplMockitoTest {
 
   @Test
   public void testCreateBucketFailure() {
-    doThrow(STORAGE_FAILURE).when(storageRpcMock).create(BUCKET_INFO1.toPb(), EMPTY_RPC_OPTIONS);
+    doThrow(STORAGE_FAILURE)
+        .when(storageRpcMock)
+        .create(Conversions.apiary().bucketInfo().encode(BUCKET_INFO1), EMPTY_RPC_OPTIONS);
     initializeService();
     try {
       storage.create(BUCKET_INFO1);
@@ -510,10 +512,12 @@ public class StorageImplMockitoTest {
 
   @Test
   public void testGetBucket() {
-    doReturn(BUCKET_INFO1.toPb())
+    doReturn(Conversions.apiary().bucketInfo().encode(BUCKET_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .get(BucketInfo.of(BUCKET_NAME1).toPb(), EMPTY_RPC_OPTIONS);
+        .get(
+            Conversions.apiary().bucketInfo().encode(BucketInfo.of(BUCKET_NAME1)),
+            EMPTY_RPC_OPTIONS);
     initializeService();
     Bucket bucket = storage.get(BUCKET_NAME1);
     assertEquals(expectedBucket1, bucket);
@@ -521,10 +525,12 @@ public class StorageImplMockitoTest {
 
   @Test
   public void testGetBucketWithOptions() {
-    doReturn(BUCKET_INFO1.toPb())
+    doReturn(Conversions.apiary().bucketInfo().encode(BUCKET_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .get(BucketInfo.of(BUCKET_NAME1).toPb(), BUCKET_GET_OPTIONS);
+        .get(
+            Conversions.apiary().bucketInfo().encode(BucketInfo.of(BUCKET_NAME1)),
+            BUCKET_GET_OPTIONS);
     initializeService();
     Bucket bucket = storage.get(BUCKET_NAME1, BUCKET_GET_METAGENERATION);
     assertEquals(expectedBucket1, bucket);
@@ -534,10 +540,12 @@ public class StorageImplMockitoTest {
   public void testGetBucketWithSelectedFields() {
     ArgumentCaptor<Map<StorageRpc.Option, Object>> capturedOptions =
         ArgumentCaptor.forClass(Map.class);
-    doReturn(BUCKET_INFO1.toPb())
+    doReturn(Conversions.apiary().bucketInfo().encode(BUCKET_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .get(Mockito.eq(BucketInfo.of(BUCKET_NAME1).toPb()), capturedOptions.capture());
+        .get(
+            Mockito.eq(Conversions.apiary().bucketInfo().encode(BucketInfo.of(BUCKET_NAME1))),
+            capturedOptions.capture());
     initializeService();
     Bucket bucket = storage.get(BUCKET_NAME1, BUCKET_GET_METAGENERATION, BUCKET_GET_FIELDS);
     assertEquals(
@@ -555,10 +563,12 @@ public class StorageImplMockitoTest {
   public void testGetBucketWithEmptyFields() {
     ArgumentCaptor<Map<StorageRpc.Option, Object>> capturedOptions =
         ArgumentCaptor.forClass(Map.class);
-    doReturn(BUCKET_INFO1.toPb())
+    doReturn(Conversions.apiary().bucketInfo().encode(BUCKET_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .get(Mockito.eq(BucketInfo.of(BUCKET_NAME1).toPb()), capturedOptions.capture());
+        .get(
+            Mockito.eq(Conversions.apiary().bucketInfo().encode(BucketInfo.of(BUCKET_NAME1))),
+            capturedOptions.capture());
     initializeService();
     Bucket bucket = storage.get(BUCKET_NAME1, BUCKET_GET_METAGENERATION, BUCKET_GET_EMPTY_FIELDS);
     assertEquals(
@@ -574,7 +584,9 @@ public class StorageImplMockitoTest {
   public void testGetBucketFailure() {
     doThrow(STORAGE_FAILURE)
         .when(storageRpcMock)
-        .get(BucketInfo.of(BUCKET_NAME1).toPb(), EMPTY_RPC_OPTIONS);
+        .get(
+            Conversions.apiary().bucketInfo().encode(BucketInfo.of(BUCKET_NAME1)),
+            EMPTY_RPC_OPTIONS);
     initializeService();
     try {
       storage.get(BUCKET_NAME1);
@@ -586,10 +598,12 @@ public class StorageImplMockitoTest {
 
   @Test
   public void testGetBlob() {
-    doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .get(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), EMPTY_RPC_OPTIONS);
+        .get(
+            Conversions.apiary().blobId().encode(BlobId.of(BUCKET_NAME1, BLOB_NAME1)),
+            EMPTY_RPC_OPTIONS);
     initializeService();
     Blob blob = storage.get(BUCKET_NAME1, BLOB_NAME1);
     assertEquals(expectedBlob1, blob);
@@ -597,10 +611,12 @@ public class StorageImplMockitoTest {
 
   @Test
   public void testGetBlobWithOptions() {
-    doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .get(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), BLOB_GET_OPTIONS);
+        .get(
+            Conversions.apiary().blobId().encode(BlobId.of(BUCKET_NAME1, BLOB_NAME1)),
+            BLOB_GET_OPTIONS);
     initializeService();
     Blob blob = storage.get(BUCKET_NAME1, BLOB_NAME1, BLOB_GET_METAGENERATION, BLOB_GET_GENERATION);
     assertEquals(expectedBlob1, blob);
@@ -608,10 +624,10 @@ public class StorageImplMockitoTest {
 
   @Test
   public void testGetBlobWithOptionsFromBlobId() {
-    doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .get(BLOB_INFO1.getBlobId().toPb(), BLOB_GET_OPTIONS);
+        .get(Conversions.apiary().blobId().encode(BLOB_INFO1.getBlobId()), BLOB_GET_OPTIONS);
     initializeService();
     Blob blob =
         storage.get(
@@ -623,10 +639,12 @@ public class StorageImplMockitoTest {
   public void testGetBlobWithSelectedFields() {
     ArgumentCaptor<Map<StorageRpc.Option, Object>> capturedOptions =
         ArgumentCaptor.forClass(Map.class);
-    doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .get(Mockito.eq(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb()), capturedOptions.capture());
+        .get(
+            Mockito.eq(Conversions.apiary().blobId().encode(BlobId.of(BUCKET_NAME1, BLOB_NAME1))),
+            capturedOptions.capture());
     initializeService();
     Blob blob =
         storage.get(
@@ -654,10 +672,12 @@ public class StorageImplMockitoTest {
   public void testGetBlobWithEmptyFields() {
     ArgumentCaptor<Map<StorageRpc.Option, Object>> capturedOptions =
         ArgumentCaptor.forClass(Map.class);
-    doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .get(Mockito.eq(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb()), capturedOptions.capture());
+        .get(
+            Mockito.eq(Conversions.apiary().blobId().encode(BlobId.of(BUCKET_NAME1, BLOB_NAME1))),
+            capturedOptions.capture());
     initializeService();
     Blob blob =
         storage.get(
@@ -683,7 +703,9 @@ public class StorageImplMockitoTest {
   public void testGetBlobFailure() {
     doThrow(STORAGE_FAILURE)
         .when(storageRpcMock)
-        .get(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), EMPTY_RPC_OPTIONS);
+        .get(
+            Conversions.apiary().blobId().encode(BlobId.of(BUCKET_NAME1, BLOB_NAME1)),
+            EMPTY_RPC_OPTIONS);
     initializeService();
     try {
       storage.get(BUCKET_NAME1, BLOB_NAME1);
@@ -706,11 +728,11 @@ public class StorageImplMockitoTest {
   public void testCreateBlob() throws IOException {
     ArgumentCaptor<ByteArrayInputStream> capturedStream =
         ArgumentCaptor.forClass(ByteArrayInputStream.class);
-    doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
         .create(
-            Mockito.eq(BLOB_INFO_WITH_HASHES.toPb()),
+            Mockito.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_HASHES)),
             capturedStream.capture(),
             Mockito.eq(EMPTY_RPC_OPTIONS));
     initializeService();
@@ -725,17 +747,19 @@ public class StorageImplMockitoTest {
   public void testCreateBlobWithSubArrayFromByteArray() throws IOException {
     ArgumentCaptor<ByteArrayInputStream> capturedStream =
         ArgumentCaptor.forClass(ByteArrayInputStream.class);
-    doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
         .create(
             Mockito.eq(
-                BLOB_INFO1
-                    .toBuilder()
-                    .setMd5(SUB_CONTENT_MD5)
-                    .setCrc32c(SUB_CONTENT_CRC32C)
-                    .build()
-                    .toPb()),
+                Conversions.apiary()
+                    .blobInfo()
+                    .encode(
+                        BLOB_INFO1
+                            .toBuilder()
+                            .setMd5(SUB_CONTENT_MD5)
+                            .setCrc32c(SUB_CONTENT_CRC32C)
+                            .build())),
             capturedStream.capture(),
             Mockito.eq(EMPTY_RPC_OPTIONS));
     initializeService();
@@ -755,10 +779,10 @@ public class StorageImplMockitoTest {
     ArgumentCaptor<ByteArrayInputStream> capturedStream =
         ArgumentCaptor.forClass(ByteArrayInputStream.class);
 
-    StorageObject storageObject = BLOB_INFO_WITH_HASHES.toPb();
+    StorageObject storageObject = Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_HASHES);
 
     doThrow(new StorageException(500, "internalError"))
-        .doReturn(BLOB_INFO1.toPb())
+        .doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
         .create(
@@ -791,17 +815,19 @@ public class StorageImplMockitoTest {
     ArgumentCaptor<ByteArrayInputStream> capturedStream =
         ArgumentCaptor.forClass(ByteArrayInputStream.class);
 
-    doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
         .create(
             Mockito.eq(
-                BLOB_INFO1
-                    .toBuilder()
-                    .setMd5("1B2M2Y8AsgTpgAmY7PhCfg==")
-                    .setCrc32c("AAAAAA==")
-                    .build()
-                    .toPb()),
+                Conversions.apiary()
+                    .blobInfo()
+                    .encode(
+                        BLOB_INFO1
+                            .toBuilder()
+                            .setMd5("1B2M2Y8AsgTpgAmY7PhCfg==")
+                            .setCrc32c("AAAAAA==")
+                            .build())),
             capturedStream.capture(),
             Mockito.eq(EMPTY_RPC_OPTIONS));
     initializeService();
@@ -818,11 +844,11 @@ public class StorageImplMockitoTest {
     ArgumentCaptor<ByteArrayInputStream> capturedStream =
         ArgumentCaptor.forClass(ByteArrayInputStream.class);
 
-    doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
         .create(
-            Mockito.eq(BLOB_INFO_WITH_HASHES.toPb()),
+            Mockito.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_HASHES)),
             capturedStream.capture(),
             Mockito.eq(BLOB_TARGET_OPTIONS_CREATE));
     initializeService();
@@ -843,11 +869,11 @@ public class StorageImplMockitoTest {
     ArgumentCaptor<ByteArrayInputStream> capturedStream =
         ArgumentCaptor.forClass(ByteArrayInputStream.class);
 
-    doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
         .create(
-            Mockito.eq(BLOB_INFO_WITH_HASHES.toPb()),
+            Mockito.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_HASHES)),
             capturedStream.capture(),
             Mockito.eq(BLOB_TARGET_OPTIONS_CREATE_DISABLE_GZIP_CONTENT));
     initializeService();
@@ -862,12 +888,12 @@ public class StorageImplMockitoTest {
     ArgumentCaptor<ByteArrayInputStream> capturedStream =
         ArgumentCaptor.forClass(ByteArrayInputStream.class);
 
-    doReturn(BLOB_INFO1.toPb())
-        .doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
+        .doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
         .create(
-            Mockito.eq(BLOB_INFO_WITH_HASHES.toPb()),
+            Mockito.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_HASHES)),
             capturedStream.capture(),
             Mockito.eq(ENCRYPTION_KEY_OPTIONS));
     initializeService();
@@ -888,12 +914,12 @@ public class StorageImplMockitoTest {
     ArgumentCaptor<ByteArrayInputStream> capturedStream =
         ArgumentCaptor.forClass(ByteArrayInputStream.class);
 
-    doReturn(BLOB_INFO1.toPb())
-        .doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
+        .doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
         .create(
-            Mockito.eq(BLOB_INFO_WITH_HASHES.toPb()),
+            Mockito.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_HASHES)),
             capturedStream.capture(),
             Mockito.eq(KMS_KEY_NAME_OPTIONS));
     initializeService();
@@ -916,11 +942,11 @@ public class StorageImplMockitoTest {
 
     ByteArrayInputStream fileStream = new ByteArrayInputStream(BLOB_CONTENT);
 
-    doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
         .create(
-            Mockito.eq(BLOB_INFO_WITHOUT_HASHES.toPb()),
+            Mockito.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO_WITHOUT_HASHES)),
             capturedStream.capture(),
             Mockito.eq(EMPTY_RPC_OPTIONS));
     initializeService();
@@ -938,11 +964,11 @@ public class StorageImplMockitoTest {
         ArgumentCaptor.forClass(ByteArrayInputStream.class);
 
     ByteArrayInputStream fileStream = new ByteArrayInputStream(BLOB_CONTENT);
-    doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
         .create(
-            Mockito.eq(BLOB_INFO_WITHOUT_HASHES.toPb()),
+            Mockito.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO_WITHOUT_HASHES)),
             capturedStream.capture(),
             Mockito.eq(BLOB_TARGET_OPTIONS_CREATE_DISABLE_GZIP_CONTENT));
     initializeService();
@@ -960,11 +986,14 @@ public class StorageImplMockitoTest {
   public void testCreateBlobFromStreamWithEncryptionKey() throws IOException {
     ByteArrayInputStream fileStream = new ByteArrayInputStream(BLOB_CONTENT);
 
-    doReturn(BLOB_INFO1.toPb())
-        .doReturn(BLOB_INFO1.toPb())
+    doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
+        .doReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .create(BLOB_INFO_WITHOUT_HASHES.toPb(), fileStream, ENCRYPTION_KEY_OPTIONS);
+        .create(
+            Conversions.apiary().blobInfo().encode(BLOB_INFO_WITHOUT_HASHES),
+            fileStream,
+            ENCRYPTION_KEY_OPTIONS);
     initializeService();
     Blob blob =
         storage.create(
@@ -985,7 +1014,10 @@ public class StorageImplMockitoTest {
     Exception internalErrorException = new StorageException(500, "internalError");
     doThrow(internalErrorException)
         .when(storageRpcMock)
-        .create(BLOB_INFO_WITHOUT_HASHES.toPb(), fileStream, EMPTY_RPC_OPTIONS);
+        .create(
+            Conversions.apiary().blobInfo().encode(BLOB_INFO_WITHOUT_HASHES),
+            fileStream,
+            EMPTY_RPC_OPTIONS);
 
     storage =
         options
@@ -1037,7 +1069,7 @@ public class StorageImplMockitoTest {
     doReturn(uploadId)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .open(blobInfo.toPb(), rpcOptions);
+        .open(Conversions.apiary().blobInfo().encode(blobInfo), rpcOptions);
 
     doReturn(storageObject)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
@@ -1045,7 +1077,8 @@ public class StorageImplMockitoTest {
         .writeWithResponse(uploadId, buffer, 0, 0L, bytes.length, true);
 
     initializeService();
-    expectedUpdated = Blob.fromPb(storage, storageObject);
+    BlobInfo info = Conversions.apiary().blobInfo().decode(storageObject);
+    expectedUpdated = info.asBlob(storage);
     return blobInfo;
   }
 
@@ -1127,7 +1160,7 @@ public class StorageImplMockitoTest {
     doReturn(uploadId)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .open(info.toPb(), EMPTY_RPC_OPTIONS);
+        .open(Conversions.apiary().blobInfo().encode(info), EMPTY_RPC_OPTIONS);
 
     Exception runtimeException = new RuntimeException("message");
     doThrow(runtimeException)
@@ -1162,7 +1195,7 @@ public class StorageImplMockitoTest {
     doReturn(uploadId)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .open(info.toPb(), EMPTY_RPC_OPTIONS);
+        .open(Conversions.apiary().blobInfo().encode(info), EMPTY_RPC_OPTIONS);
 
     byte[] buffer1 = new byte[MIN_BUFFER_SIZE];
     System.arraycopy(dataToSend, 0, buffer1, 0, MIN_BUFFER_SIZE);
@@ -1180,7 +1213,8 @@ public class StorageImplMockitoTest {
 
     InputStream input = new ByteArrayInputStream(dataToSend);
     Blob blob = storage.createFrom(info, input, MIN_BUFFER_SIZE);
-    assertEquals(Blob.fromPb(storage, storageObject), blob);
+    BlobInfo info1 = Conversions.apiary().blobInfo().decode(storageObject);
+    assertEquals(info1.asBlob(storage), blob);
   }
 
   @Test
@@ -1188,7 +1222,8 @@ public class StorageImplMockitoTest {
     String cursor = "cursor";
     ImmutableList<BucketInfo> bucketInfoList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
     Tuple<String, Iterable<com.google.api.services.storage.model.Bucket>> result =
-        Tuple.of(cursor, Iterables.transform(bucketInfoList, BucketInfo.TO_PB_FUNCTION));
+        Tuple.of(
+            cursor, Iterables.transform(bucketInfoList, Conversions.apiary().bucketInfo()::encode));
 
     doReturn(result)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
@@ -1221,7 +1256,8 @@ public class StorageImplMockitoTest {
     String cursor = "cursor";
     ImmutableList<BucketInfo> bucketInfoList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
     Tuple<String, Iterable<com.google.api.services.storage.model.Bucket>> result =
-        Tuple.of(cursor, Iterables.transform(bucketInfoList, BucketInfo.TO_PB_FUNCTION));
+        Tuple.of(
+            cursor, Iterables.transform(bucketInfoList, Conversions.apiary().bucketInfo()::encode));
 
     doReturn(result)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
@@ -1243,7 +1279,8 @@ public class StorageImplMockitoTest {
 
     ImmutableList<BucketInfo> bucketInfoList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
     Tuple<String, Iterable<com.google.api.services.storage.model.Bucket>> result =
-        Tuple.of(cursor, Iterables.transform(bucketInfoList, BucketInfo.TO_PB_FUNCTION));
+        Tuple.of(
+            cursor, Iterables.transform(bucketInfoList, Conversions.apiary().bucketInfo()::encode));
 
     doReturn(result)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
@@ -1271,7 +1308,8 @@ public class StorageImplMockitoTest {
         ArgumentCaptor.forClass(Map.class);
     ImmutableList<BucketInfo> bucketInfoList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
     Tuple<String, Iterable<com.google.api.services.storage.model.Bucket>> result =
-        Tuple.of(cursor, Iterables.transform(bucketInfoList, BucketInfo.TO_PB_FUNCTION));
+        Tuple.of(
+            cursor, Iterables.transform(bucketInfoList, Conversions.apiary().bucketInfo()::encode));
 
     doReturn(result)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
@@ -1308,7 +1346,8 @@ public class StorageImplMockitoTest {
     String cursor = "cursor";
     ImmutableList<BlobInfo> blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple<String, Iterable<com.google.api.services.storage.model.StorageObject>> result =
-        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
+        Tuple.of(
+            cursor, Iterables.transform(blobInfoList, Conversions.apiary().blobInfo()::encode));
 
     doReturn(result)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
@@ -1343,7 +1382,8 @@ public class StorageImplMockitoTest {
     String cursor = "cursor";
     ImmutableList<BlobInfo> blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple<String, Iterable<com.google.api.services.storage.model.StorageObject>> result =
-        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
+        Tuple.of(
+            cursor, Iterables.transform(blobInfoList, Conversions.apiary().blobInfo()::encode));
     doReturn(result)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
@@ -1363,7 +1403,8 @@ public class StorageImplMockitoTest {
         ArgumentCaptor.forClass(Map.class);
     ImmutableList<BlobInfo> blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple<String, Iterable<com.google.api.services.storage.model.StorageObject>> result =
-        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
+        Tuple.of(
+            cursor, Iterables.transform(blobInfoList, Conversions.apiary().blobInfo()::encode));
     doReturn(result)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
@@ -1400,7 +1441,8 @@ public class StorageImplMockitoTest {
         ArgumentCaptor.forClass(Map.class);
     ImmutableList<BlobInfo> blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple<String, Iterable<com.google.api.services.storage.model.StorageObject>> result =
-        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
+        Tuple.of(
+            cursor, Iterables.transform(blobInfoList, Conversions.apiary().blobInfo()::encode));
     doReturn(result)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
@@ -1435,7 +1477,8 @@ public class StorageImplMockitoTest {
     Map<StorageRpc.Option, ?> options = ImmutableMap.of(StorageRpc.Option.DELIMITER, "/");
     ImmutableList<BlobInfo> blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple<String, Iterable<com.google.api.services.storage.model.StorageObject>> result =
-        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
+        Tuple.of(
+            cursor, Iterables.transform(blobInfoList, Conversions.apiary().blobInfo()::encode));
     doReturn(result)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
@@ -1455,7 +1498,8 @@ public class StorageImplMockitoTest {
     Map<StorageRpc.Option, ?> options = ImmutableMap.of(StorageRpc.Option.DELIMITER, delimiter);
     ImmutableList<BlobInfo> blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple<String, Iterable<com.google.api.services.storage.model.StorageObject>> result =
-        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
+        Tuple.of(
+            cursor, Iterables.transform(blobInfoList, Conversions.apiary().blobInfo()::encode));
     doReturn(result)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
@@ -1478,7 +1522,8 @@ public class StorageImplMockitoTest {
             StorageRpc.Option.START_OFF_SET, startOffset, StorageRpc.Option.END_OFF_SET, endOffset);
     ImmutableList<BlobInfo> blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple<String, Iterable<com.google.api.services.storage.model.StorageObject>> result =
-        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
+        Tuple.of(
+            cursor, Iterables.transform(blobInfoList, Conversions.apiary().blobInfo()::encode));
     doReturn(result)
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
@@ -1541,7 +1586,11 @@ public class StorageImplMockitoTest {
     doReturn(Tuple.of("etag", BLOB_CONTENT))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .read(BLOB_INFO2.toPb(), BLOB_SOURCE_OPTIONS, 0, DEFAULT_CHUNK_SIZE);
+        .read(
+            Conversions.apiary().blobInfo().encode(BLOB_INFO2),
+            BLOB_SOURCE_OPTIONS,
+            0,
+            DEFAULT_CHUNK_SIZE);
     initializeService();
     ReadChannel channel =
         storage.reader(
@@ -1554,7 +1603,11 @@ public class StorageImplMockitoTest {
     doReturn(Tuple.of("a", BLOB_CONTENT), Tuple.of("b", BLOB_SUB_CONTENT))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .read(BLOB_INFO2.toPb(), ENCRYPTION_KEY_OPTIONS, 0, DEFAULT_CHUNK_SIZE);
+        .read(
+            Conversions.apiary().blobInfo().encode(BLOB_INFO2),
+            ENCRYPTION_KEY_OPTIONS,
+            0,
+            DEFAULT_CHUNK_SIZE);
     initializeService();
     ReadChannel channel =
         storage.reader(BUCKET_NAME1, BLOB_NAME2, Storage.BlobSourceOption.decryptionKey(KEY));
@@ -1571,7 +1624,11 @@ public class StorageImplMockitoTest {
     doReturn(Tuple.of("etag", BLOB_CONTENT))
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .read(BLOB_INFO1.getBlobId().toPb(), BLOB_SOURCE_OPTIONS, 0, DEFAULT_CHUNK_SIZE);
+        .read(
+            Conversions.apiary().blobId().encode(BLOB_INFO1.getBlobId()),
+            BLOB_SOURCE_OPTIONS,
+            0,
+            DEFAULT_CHUNK_SIZE);
     initializeService();
     ReadChannel channel =
         storage.reader(
@@ -1585,7 +1642,11 @@ public class StorageImplMockitoTest {
   public void testReaderFailure() throws IOException {
     doThrow(STORAGE_FAILURE)
         .when(storageRpcMock)
-        .read(BLOB_INFO2.getBlobId().toPb(), EMPTY_RPC_OPTIONS, 0, DEFAULT_CHUNK_SIZE);
+        .read(
+            Conversions.apiary().blobId().encode(BLOB_INFO2.getBlobId()),
+            EMPTY_RPC_OPTIONS,
+            0,
+            DEFAULT_CHUNK_SIZE);
     initializeService();
     ReadChannel channel = storage.reader(BUCKET_NAME1, BLOB_NAME2);
     assertNotNull(channel);
@@ -1603,7 +1664,7 @@ public class StorageImplMockitoTest {
     doReturn("upload-id")
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .open(BLOB_INFO_WITHOUT_HASHES.toPb(), EMPTY_RPC_OPTIONS);
+        .open(Conversions.apiary().blobInfo().encode(BLOB_INFO_WITHOUT_HASHES), EMPTY_RPC_OPTIONS);
     initializeService();
     WriteChannel channel = storage.writer(BLOB_INFO_WITH_HASHES);
     assertNotNull(channel);
@@ -1616,7 +1677,7 @@ public class StorageImplMockitoTest {
     doReturn("upload-id")
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .open(info.toPb(), BLOB_TARGET_OPTIONS_CREATE);
+        .open(Conversions.apiary().blobInfo().encode(info), BLOB_TARGET_OPTIONS_CREATE);
     initializeService();
     WriteChannel channel =
         storage.writer(
@@ -1636,7 +1697,7 @@ public class StorageImplMockitoTest {
     doReturn("upload-id-1", "upload-id-2")
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .open(info.toPb(), ENCRYPTION_KEY_OPTIONS);
+        .open(Conversions.apiary().blobInfo().encode(info), ENCRYPTION_KEY_OPTIONS);
     initializeService();
     WriteChannel channel = storage.writer(info, Storage.BlobWriteOption.encryptionKey(KEY));
     assertNotNull(channel);
@@ -1652,7 +1713,7 @@ public class StorageImplMockitoTest {
     doReturn("upload-id-1", "upload-id-2")
         .doThrow(UNEXPECTED_CALL_EXCEPTION)
         .when(storageRpcMock)
-        .open(info.toPb(), KMS_KEY_NAME_OPTIONS);
+        .open(Conversions.apiary().blobInfo().encode(info), KMS_KEY_NAME_OPTIONS);
     initializeService();
     WriteChannel channel = storage.writer(info, Storage.BlobWriteOption.kmsKeyName(KMS_KEY_NAME));
     assertNotNull(channel);
@@ -1666,7 +1727,7 @@ public class StorageImplMockitoTest {
   public void testWriterFailure() {
     doThrow(STORAGE_FAILURE)
         .when(storageRpcMock)
-        .open(BLOB_INFO_WITHOUT_HASHES.toPb(), EMPTY_RPC_OPTIONS);
+        .open(Conversions.apiary().blobInfo().encode(BLOB_INFO_WITHOUT_HASHES), EMPTY_RPC_OPTIONS);
     initializeService();
     try {
       storage.writer(BLOB_INFO_WITH_HASHES);
@@ -1678,9 +1739,10 @@ public class StorageImplMockitoTest {
 
   @Test
   public void testCreateNotification() {
-    doReturn(NOTIFICATION_INFO_01.toPb())
+    doReturn(Conversions.apiary().notificationInfo().encode(NOTIFICATION_INFO_01))
         .when(storageRpcMock)
-        .createNotification(BUCKET_NAME1, NOTIFICATION_INFO_01.toPb());
+        .createNotification(
+            BUCKET_NAME1, Conversions.apiary().notificationInfo().encode(NOTIFICATION_INFO_01));
     initializeService();
     Notification notification = storage.createNotification(BUCKET_NAME1, NOTIFICATION_INFO_01);
     verifyBucketNotification(notification);
@@ -1688,7 +1750,7 @@ public class StorageImplMockitoTest {
 
   @Test
   public void testGetNotification() {
-    doReturn(NOTIFICATION_INFO_01.toPb())
+    doReturn(Conversions.apiary().notificationInfo().encode(NOTIFICATION_INFO_01))
         .when(storageRpcMock)
         .getNotification(BUCKET_NAME1, GENERATED_ID);
     initializeService();
@@ -1698,7 +1760,10 @@ public class StorageImplMockitoTest {
 
   @Test
   public void testListNotification() {
-    doReturn(Arrays.asList(NOTIFICATION_INFO_01.toPb(), NOTIFICATION_INFO_02.toPb()))
+    doReturn(
+            Arrays.asList(
+                Conversions.apiary().notificationInfo().encode(NOTIFICATION_INFO_01),
+                Conversions.apiary().notificationInfo().encode(NOTIFICATION_INFO_02)))
         .when(storageRpcMock)
         .listNotifications(BUCKET_NAME1);
     initializeService();
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java
index ae368241..c4651fbd 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java
@@ -438,8 +438,10 @@ public class StorageImplTest {
   @Test
   public void testUpdateBucket() {
     BucketInfo updatedBucketInfo = BUCKET_INFO1.toBuilder().setIndexPage("some-page").build();
-    EasyMock.expect(storageRpcMock.patch(updatedBucketInfo.toPb(), EMPTY_RPC_OPTIONS))
-        .andReturn(updatedBucketInfo.toPb());
+    EasyMock.expect(
+            storageRpcMock.patch(
+                Conversions.apiary().bucketInfo().encode(updatedBucketInfo), EMPTY_RPC_OPTIONS))
+        .andReturn(Conversions.apiary().bucketInfo().encode(updatedBucketInfo));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Bucket bucket = storage.update(updatedBucketInfo);
@@ -449,8 +451,10 @@ public class StorageImplTest {
   @Test
   public void testUpdateBucketWithOptions() {
     BucketInfo updatedBucketInfo = BUCKET_INFO1.toBuilder().setIndexPage("some-page").build();
-    EasyMock.expect(storageRpcMock.patch(updatedBucketInfo.toPb(), BUCKET_TARGET_OPTIONS))
-        .andReturn(updatedBucketInfo.toPb());
+    EasyMock.expect(
+            storageRpcMock.patch(
+                Conversions.apiary().bucketInfo().encode(updatedBucketInfo), BUCKET_TARGET_OPTIONS))
+        .andReturn(Conversions.apiary().bucketInfo().encode(updatedBucketInfo));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Bucket bucket =
@@ -462,8 +466,10 @@ public class StorageImplTest {
   @Test
   public void testUpdateBlob() {
     BlobInfo updatedBlobInfo = BLOB_INFO1.toBuilder().setContentType("some-content-type").build();
-    EasyMock.expect(storageRpcMock.patch(updatedBlobInfo.toPb(), EMPTY_RPC_OPTIONS))
-        .andReturn(updatedBlobInfo.toPb());
+    EasyMock.expect(
+            storageRpcMock.patch(
+                Conversions.apiary().blobInfo().encode(updatedBlobInfo), EMPTY_RPC_OPTIONS))
+        .andReturn(Conversions.apiary().blobInfo().encode(updatedBlobInfo));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Blob blob = storage.update(updatedBlobInfo);
@@ -473,8 +479,11 @@ public class StorageImplTest {
   @Test
   public void testUpdateBlobWithOptions() {
     BlobInfo updatedBlobInfo = BLOB_INFO1.toBuilder().setContentType("some-content-type").build();
-    EasyMock.expect(storageRpcMock.patch(updatedBlobInfo.toPb(), BLOB_TARGET_OPTIONS_UPDATE))
-        .andReturn(updatedBlobInfo.toPb());
+    EasyMock.expect(
+            storageRpcMock.patch(
+                Conversions.apiary().blobInfo().encode(updatedBlobInfo),
+                BLOB_TARGET_OPTIONS_UPDATE))
+        .andReturn(Conversions.apiary().blobInfo().encode(updatedBlobInfo));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Blob blob =
@@ -484,7 +493,10 @@ public class StorageImplTest {
 
   @Test
   public void testDeleteBucket() {
-    EasyMock.expect(storageRpcMock.delete(BucketInfo.of(BUCKET_NAME1).toPb(), EMPTY_RPC_OPTIONS))
+    EasyMock.expect(
+            storageRpcMock.delete(
+                Conversions.apiary().bucketInfo().encode(BucketInfo.of(BUCKET_NAME1)),
+                EMPTY_RPC_OPTIONS))
         .andReturn(true);
     EasyMock.replay(storageRpcMock);
     initializeService();
@@ -494,7 +506,9 @@ public class StorageImplTest {
   @Test
   public void testDeleteBucketWithOptions() {
     EasyMock.expect(
-            storageRpcMock.delete(BucketInfo.of(BUCKET_NAME1).toPb(), BUCKET_SOURCE_OPTIONS))
+            storageRpcMock.delete(
+                Conversions.apiary().bucketInfo().encode(BucketInfo.of(BUCKET_NAME1)),
+                BUCKET_SOURCE_OPTIONS))
         .andReturn(true);
     EasyMock.replay(storageRpcMock);
     initializeService();
@@ -504,7 +518,9 @@ public class StorageImplTest {
   @Test
   public void testDeleteBlob() {
     EasyMock.expect(
-            storageRpcMock.delete(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), EMPTY_RPC_OPTIONS))
+            storageRpcMock.delete(
+                Conversions.apiary().blobId().encode(BlobId.of(BUCKET_NAME1, BLOB_NAME1)),
+                EMPTY_RPC_OPTIONS))
         .andReturn(true);
     EasyMock.replay(storageRpcMock);
     initializeService();
@@ -514,7 +530,9 @@ public class StorageImplTest {
   @Test
   public void testDeleteBlobWithOptions() {
     EasyMock.expect(
-            storageRpcMock.delete(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), BLOB_SOURCE_OPTIONS))
+            storageRpcMock.delete(
+                Conversions.apiary().blobId().encode(BlobId.of(BUCKET_NAME1, BLOB_NAME1)),
+                BLOB_SOURCE_OPTIONS))
         .andReturn(true);
     EasyMock.replay(storageRpcMock);
     initializeService();
@@ -525,7 +543,9 @@ public class StorageImplTest {
 
   @Test
   public void testDeleteBlobWithOptionsFromBlobId() {
-    EasyMock.expect(storageRpcMock.delete(BLOB_INFO1.getBlobId().toPb(), BLOB_SOURCE_OPTIONS))
+    EasyMock.expect(
+            storageRpcMock.delete(
+                Conversions.apiary().blobId().encode(BLOB_INFO1.getBlobId()), BLOB_SOURCE_OPTIONS))
         .andReturn(true);
     EasyMock.replay(storageRpcMock);
     initializeService();
@@ -545,10 +565,12 @@ public class StorageImplTest {
             .build();
     EasyMock.expect(
             storageRpcMock.compose(
-                ImmutableList.of(BLOB_INFO2.toPb(), BLOB_INFO3.toPb()),
-                BLOB_INFO1.toPb(),
+                ImmutableList.of(
+                    Conversions.apiary().blobInfo().encode(BLOB_INFO2),
+                    Conversions.apiary().blobInfo().encode(BLOB_INFO3)),
+                Conversions.apiary().blobInfo().encode(BLOB_INFO1),
                 EMPTY_RPC_OPTIONS))
-        .andReturn(BLOB_INFO1.toPb());
+        .andReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Blob blob = storage.compose(req);
@@ -565,10 +587,12 @@ public class StorageImplTest {
             .build();
     EasyMock.expect(
             storageRpcMock.compose(
-                ImmutableList.of(BLOB_INFO2.toPb(), BLOB_INFO3.toPb()),
-                BLOB_INFO1.toPb(),
+                ImmutableList.of(
+                    Conversions.apiary().blobInfo().encode(BLOB_INFO2),
+                    Conversions.apiary().blobInfo().encode(BLOB_INFO3)),
+                Conversions.apiary().blobInfo().encode(BLOB_INFO1),
                 BLOB_TARGET_OPTIONS_COMPOSE))
-        .andReturn(BLOB_INFO1.toPb());
+        .andReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Blob blob = storage.compose(req);
@@ -580,10 +604,10 @@ public class StorageImplTest {
     CopyRequest request = Storage.CopyRequest.of(BLOB_INFO1.getBlobId(), BLOB_INFO2.getBlobId());
     StorageRpc.RewriteRequest rpcRequest =
         new StorageRpc.RewriteRequest(
-            request.getSource().toPb(),
+            Conversions.apiary().blobId().encode(request.getSource()),
             EMPTY_RPC_OPTIONS,
             false,
-            BLOB_INFO2.toPb(),
+            Conversions.apiary().blobInfo().encode(BLOB_INFO2),
             EMPTY_RPC_OPTIONS,
             null);
     StorageRpc.RewriteResponse rpcResponse =
@@ -607,10 +631,10 @@ public class StorageImplTest {
             .build();
     StorageRpc.RewriteRequest rpcRequest =
         new StorageRpc.RewriteRequest(
-            request.getSource().toPb(),
+            Conversions.apiary().blobId().encode(request.getSource()),
             BLOB_SOURCE_OPTIONS_COPY,
             true,
-            request.getTarget().toPb(),
+            Conversions.apiary().blobInfo().encode(request.getTarget()),
             BLOB_TARGET_OPTIONS_COMPOSE,
             null);
     StorageRpc.RewriteResponse rpcResponse =
@@ -634,10 +658,10 @@ public class StorageImplTest {
             .build();
     StorageRpc.RewriteRequest rpcRequest =
         new StorageRpc.RewriteRequest(
-            request.getSource().toPb(),
+            Conversions.apiary().blobId().encode(request.getSource()),
             ENCRYPTION_KEY_OPTIONS,
             true,
-            request.getTarget().toPb(),
+            Conversions.apiary().blobInfo().encode(request.getTarget()),
             ENCRYPTION_KEY_OPTIONS,
             null);
     StorageRpc.RewriteResponse rpcResponse =
@@ -671,10 +695,10 @@ public class StorageImplTest {
             .build();
     StorageRpc.RewriteRequest rpcRequest =
         new StorageRpc.RewriteRequest(
-            request.getSource().toPb(),
+            Conversions.apiary().blobId().encode(request.getSource()),
             ENCRYPTION_KEY_OPTIONS,
             true,
-            request.getTarget().toPb(),
+            Conversions.apiary().blobInfo().encode(request.getTarget()),
             KMS_KEY_NAME_OPTIONS,
             null);
     StorageRpc.RewriteResponse rpcResponse =
@@ -708,10 +732,10 @@ public class StorageImplTest {
             .build();
     StorageRpc.RewriteRequest rpcRequest =
         new StorageRpc.RewriteRequest(
-            request.getSource().toPb(),
+            Conversions.apiary().blobId().encode(request.getSource()),
             BLOB_SOURCE_OPTIONS_COPY,
             true,
-            request.getTarget().toPb(),
+            Conversions.apiary().blobInfo().encode(request.getTarget()),
             BLOB_TARGET_OPTIONS_COMPOSE,
             null);
     StorageRpc.RewriteResponse rpcResponse =
@@ -730,16 +754,22 @@ public class StorageImplTest {
     CopyRequest request = Storage.CopyRequest.of(BLOB_INFO1.getBlobId(), BLOB_INFO2.getBlobId());
     StorageRpc.RewriteRequest rpcRequest =
         new StorageRpc.RewriteRequest(
-            request.getSource().toPb(),
+            Conversions.apiary().blobId().encode(request.getSource()),
             EMPTY_RPC_OPTIONS,
             false,
-            BLOB_INFO2.toPb(),
+            Conversions.apiary().blobInfo().encode(BLOB_INFO2),
             EMPTY_RPC_OPTIONS,
             null);
     StorageRpc.RewriteResponse rpcResponse1 =
         new StorageRpc.RewriteResponse(rpcRequest, null, 42L, false, "token", 21L);
     StorageRpc.RewriteResponse rpcResponse2 =
-        new StorageRpc.RewriteResponse(rpcRequest, BLOB_INFO1.toPb(), 42L, true, "token", 42L);
+        new StorageRpc.RewriteResponse(
+            rpcRequest,
+            Conversions.apiary().blobInfo().encode(BLOB_INFO1),
+            42L,
+            true,
+            "token",
+            42L);
     EasyMock.expect(storageRpcMock.openRewrite(rpcRequest)).andReturn(rpcResponse1);
     EasyMock.expect(storageRpcMock.continueRewrite(rpcResponse1)).andReturn(rpcResponse2);
     EasyMock.replay(storageRpcMock);
@@ -757,7 +787,9 @@ public class StorageImplTest {
   @Test
   public void testReadAllBytes() {
     EasyMock.expect(
-            storageRpcMock.load(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), EMPTY_RPC_OPTIONS))
+            storageRpcMock.load(
+                Conversions.apiary().blobId().encode(BlobId.of(BUCKET_NAME1, BLOB_NAME1)),
+                EMPTY_RPC_OPTIONS))
         .andReturn(BLOB_CONTENT);
     EasyMock.replay(storageRpcMock);
     initializeService();
@@ -768,7 +800,9 @@ public class StorageImplTest {
   @Test
   public void testReadAllBytesWithOptions() {
     EasyMock.expect(
-            storageRpcMock.load(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), BLOB_SOURCE_OPTIONS))
+            storageRpcMock.load(
+                Conversions.apiary().blobId().encode(BlobId.of(BUCKET_NAME1, BLOB_NAME1)),
+                BLOB_SOURCE_OPTIONS))
         .andReturn(BLOB_CONTENT);
     EasyMock.replay(storageRpcMock);
     initializeService();
@@ -781,7 +815,9 @@ public class StorageImplTest {
   @Test
   public void testReadAllBytesWithDecriptionKey() {
     EasyMock.expect(
-            storageRpcMock.load(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), ENCRYPTION_KEY_OPTIONS))
+            storageRpcMock.load(
+                Conversions.apiary().blobId().encode(BlobId.of(BUCKET_NAME1, BLOB_NAME1)),
+                ENCRYPTION_KEY_OPTIONS))
         .andReturn(BLOB_CONTENT)
         .times(2);
     EasyMock.replay(storageRpcMock);
@@ -796,7 +832,9 @@ public class StorageImplTest {
 
   @Test
   public void testReadAllBytesFromBlobIdWithOptions() {
-    EasyMock.expect(storageRpcMock.load(BLOB_INFO1.getBlobId().toPb(), BLOB_SOURCE_OPTIONS))
+    EasyMock.expect(
+            storageRpcMock.load(
+                Conversions.apiary().blobId().encode(BLOB_INFO1.getBlobId()), BLOB_SOURCE_OPTIONS))
         .andReturn(BLOB_CONTENT);
     EasyMock.replay(storageRpcMock);
     initializeService();
@@ -810,7 +848,10 @@ public class StorageImplTest {
 
   @Test
   public void testReadAllBytesFromBlobIdWithDecriptionKey() {
-    EasyMock.expect(storageRpcMock.load(BLOB_INFO1.getBlobId().toPb(), ENCRYPTION_KEY_OPTIONS))
+    EasyMock.expect(
+            storageRpcMock.load(
+                Conversions.apiary().blobId().encode(BLOB_INFO1.getBlobId()),
+                ENCRYPTION_KEY_OPTIONS))
         .andReturn(BLOB_CONTENT)
         .times(2);
     EasyMock.replay(storageRpcMock);
@@ -1605,11 +1646,11 @@ public class StorageImplTest {
     Capture<RpcBatch.Callback<StorageObject>> callback1 = Capture.newInstance();
     Capture<RpcBatch.Callback<StorageObject>> callback2 = Capture.newInstance();
     batchMock.addGet(
-        EasyMock.eq(blobId1.toPb()),
+        EasyMock.eq(Conversions.apiary().blobId().encode(blobId1)),
         EasyMock.capture(callback1),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     batchMock.addGet(
-        EasyMock.eq(blobId2.toPb()),
+        EasyMock.eq(Conversions.apiary().blobId().encode(blobId2)),
         EasyMock.capture(callback2),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     EasyMock.expect(storageRpcMock.createBatch()).andReturn(batchMock);
@@ -1617,7 +1658,7 @@ public class StorageImplTest {
     EasyMock.replay(storageRpcMock, batchMock);
     initializeService();
     List<Blob> resultBlobs = storage.get(blobId1, blobId2);
-    callback1.getValue().onSuccess(BLOB_INFO1.toPb());
+    callback1.getValue().onSuccess(Conversions.apiary().blobInfo().encode(BLOB_INFO1));
     callback2.getValue().onFailure(new GoogleJsonError());
     assertEquals(2, resultBlobs.size());
     assertEquals(new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO1)), resultBlobs.get(0));
@@ -1633,11 +1674,11 @@ public class StorageImplTest {
     Capture<RpcBatch.Callback<StorageObject>> callback1 = Capture.newInstance();
     Capture<RpcBatch.Callback<StorageObject>> callback2 = Capture.newInstance();
     batchMock.addGet(
-        EasyMock.eq(blobId1.toPb()),
+        EasyMock.eq(Conversions.apiary().blobId().encode(blobId1)),
         EasyMock.capture(callback1),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     batchMock.addGet(
-        EasyMock.eq(blobId2.toPb()),
+        EasyMock.eq(Conversions.apiary().blobId().encode(blobId2)),
         EasyMock.capture(callback2),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     EasyMock.expect(storageRpcMock.createBatch()).andReturn(batchMock);
@@ -1645,7 +1686,7 @@ public class StorageImplTest {
     EasyMock.replay(storageRpcMock, batchMock);
     initializeService();
     List<Blob> resultBlobs = storage.get(ImmutableList.of(blobId1, blobId2));
-    callback1.getValue().onSuccess(BLOB_INFO1.toPb());
+    callback1.getValue().onSuccess(Conversions.apiary().blobInfo().encode(BLOB_INFO1));
     callback2.getValue().onFailure(new GoogleJsonError());
     assertEquals(2, resultBlobs.size());
     assertEquals(new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO1)), resultBlobs.get(0));
@@ -1661,11 +1702,11 @@ public class StorageImplTest {
     Capture<RpcBatch.Callback<Void>> callback1 = Capture.newInstance();
     Capture<RpcBatch.Callback<Void>> callback2 = Capture.newInstance();
     batchMock.addDelete(
-        EasyMock.eq(blobId1.toPb()),
+        EasyMock.eq(Conversions.apiary().blobId().encode(blobId1)),
         EasyMock.capture(callback1),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     batchMock.addDelete(
-        EasyMock.eq(blobId2.toPb()),
+        EasyMock.eq(Conversions.apiary().blobId().encode(blobId2)),
         EasyMock.capture(callback2),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     EasyMock.expect(storageRpcMock.createBatch()).andReturn(batchMock);
@@ -1689,11 +1730,11 @@ public class StorageImplTest {
     Capture<RpcBatch.Callback<Void>> callback1 = Capture.newInstance();
     Capture<RpcBatch.Callback<Void>> callback2 = Capture.newInstance();
     batchMock.addDelete(
-        EasyMock.eq(blobId1.toPb()),
+        EasyMock.eq(Conversions.apiary().blobId().encode(blobId1)),
         EasyMock.capture(callback1),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     batchMock.addDelete(
-        EasyMock.eq(blobId2.toPb()),
+        EasyMock.eq(Conversions.apiary().blobId().encode(blobId2)),
         EasyMock.capture(callback2),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     EasyMock.expect(storageRpcMock.createBatch()).andReturn(batchMock);
@@ -1715,11 +1756,11 @@ public class StorageImplTest {
     Capture<RpcBatch.Callback<StorageObject>> callback1 = Capture.newInstance();
     Capture<RpcBatch.Callback<StorageObject>> callback2 = Capture.newInstance();
     batchMock.addPatch(
-        EasyMock.eq(BLOB_INFO1.toPb()),
+        EasyMock.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO1)),
         EasyMock.capture(callback1),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     batchMock.addPatch(
-        EasyMock.eq(BLOB_INFO2.toPb()),
+        EasyMock.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO2)),
         EasyMock.capture(callback2),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     EasyMock.expect(storageRpcMock.createBatch()).andReturn(batchMock);
@@ -1727,7 +1768,7 @@ public class StorageImplTest {
     EasyMock.replay(storageRpcMock, batchMock);
     initializeService();
     List<Blob> resultBlobs = storage.update(BLOB_INFO1, BLOB_INFO2);
-    callback1.getValue().onSuccess(BLOB_INFO1.toPb());
+    callback1.getValue().onSuccess(Conversions.apiary().blobInfo().encode(BLOB_INFO1));
     callback2.getValue().onFailure(new GoogleJsonError());
     assertEquals(2, resultBlobs.size());
     assertEquals(new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO1)), resultBlobs.get(0));
@@ -1741,11 +1782,11 @@ public class StorageImplTest {
     Capture<RpcBatch.Callback<StorageObject>> callback1 = Capture.newInstance();
     Capture<RpcBatch.Callback<StorageObject>> callback2 = Capture.newInstance();
     batchMock.addPatch(
-        EasyMock.eq(BLOB_INFO1.toPb()),
+        EasyMock.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO1)),
         EasyMock.capture(callback1),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     batchMock.addPatch(
-        EasyMock.eq(BLOB_INFO2.toPb()),
+        EasyMock.eq(Conversions.apiary().blobInfo().encode(BLOB_INFO2)),
         EasyMock.capture(callback2),
         EasyMock.eq(ImmutableMap.<StorageRpc.Option, Object>of()));
     EasyMock.expect(storageRpcMock.createBatch()).andReturn(batchMock);
@@ -1753,7 +1794,7 @@ public class StorageImplTest {
     EasyMock.replay(storageRpcMock, batchMock);
     initializeService();
     List<Blob> resultBlobs = storage.update(ImmutableList.of(BLOB_INFO1, BLOB_INFO2));
-    callback1.getValue().onSuccess(BLOB_INFO1.toPb());
+    callback1.getValue().onSuccess(Conversions.apiary().blobInfo().encode(BLOB_INFO1));
     callback2.getValue().onFailure(new GoogleJsonError());
     assertEquals(2, resultBlobs.size());
     assertEquals(new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO1)), resultBlobs.get(0));
@@ -1766,7 +1807,7 @@ public class StorageImplTest {
     EasyMock.expect(
             storageRpcMock.getAcl(
                 BUCKET_NAME1, "allAuthenticatedUsers", new HashMap<StorageRpc.Option, Object>()))
-        .andReturn(ACL.toBucketPb());
+        .andReturn(Conversions.apiary().bucketAcl().encode(ACL));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Acl acl = storage.getAcl(BUCKET_NAME1, User.ofAllAuthenticatedUsers());
@@ -1800,8 +1841,9 @@ public class StorageImplTest {
     Acl returnedAcl = ACL.toBuilder().setEtag("ETAG").setId("ID").build();
     EasyMock.expect(
             storageRpcMock.createAcl(
-                ACL.toBucketPb().setBucket(BUCKET_NAME1), new HashMap<StorageRpc.Option, Object>()))
-        .andReturn(returnedAcl.toBucketPb());
+                Conversions.apiary().bucketAcl().encode(ACL).setBucket(BUCKET_NAME1),
+                new HashMap<StorageRpc.Option, Object>()))
+        .andReturn(Conversions.apiary().bucketAcl().encode(returnedAcl));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Acl acl = storage.createAcl(BUCKET_NAME1, ACL);
@@ -1813,8 +1855,9 @@ public class StorageImplTest {
     Acl returnedAcl = ACL.toBuilder().setEtag("ETAG").setId("ID").build();
     EasyMock.expect(
             storageRpcMock.patchAcl(
-                ACL.toBucketPb().setBucket(BUCKET_NAME1), new HashMap<StorageRpc.Option, Object>()))
-        .andReturn(returnedAcl.toBucketPb());
+                Conversions.apiary().bucketAcl().encode(ACL).setBucket(BUCKET_NAME1),
+                new HashMap<StorageRpc.Option, Object>()))
+        .andReturn(Conversions.apiary().bucketAcl().encode(returnedAcl));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Acl acl = storage.updateAcl(BUCKET_NAME1, ACL);
@@ -1824,7 +1867,10 @@ public class StorageImplTest {
   @Test
   public void testListBucketAcl() {
     EasyMock.expect(storageRpcMock.listAcls(BUCKET_NAME1, new HashMap<StorageRpc.Option, Object>()))
-        .andReturn(ImmutableList.of(ACL.toBucketPb(), OTHER_ACL.toBucketPb()));
+        .andReturn(
+            ImmutableList.of(
+                Conversions.apiary().bucketAcl().encode(ACL),
+                Conversions.apiary().bucketAcl().encode(OTHER_ACL)));
     EasyMock.replay(storageRpcMock);
     initializeService();
     List<Acl> acls = storage.listAcls(BUCKET_NAME1);
@@ -1834,7 +1880,7 @@ public class StorageImplTest {
   @Test
   public void testGetDefaultBucketAcl() {
     EasyMock.expect(storageRpcMock.getDefaultAcl(BUCKET_NAME1, "allAuthenticatedUsers"))
-        .andReturn(ACL.toObjectPb());
+        .andReturn(Conversions.apiary().objectAcl().encode(ACL));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Acl acl = storage.getDefaultAcl(BUCKET_NAME1, User.ofAllAuthenticatedUsers());
@@ -1862,8 +1908,10 @@ public class StorageImplTest {
   @Test
   public void testCreateDefaultBucketAcl() {
     Acl returnedAcl = ACL.toBuilder().setEtag("ETAG").setId("ID").build();
-    EasyMock.expect(storageRpcMock.createDefaultAcl(ACL.toObjectPb().setBucket(BUCKET_NAME1)))
-        .andReturn(returnedAcl.toObjectPb());
+    EasyMock.expect(
+            storageRpcMock.createDefaultAcl(
+                Conversions.apiary().objectAcl().encode(ACL).setBucket(BUCKET_NAME1)))
+        .andReturn(Conversions.apiary().objectAcl().encode(returnedAcl));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Acl acl = storage.createDefaultAcl(BUCKET_NAME1, ACL);
@@ -1873,8 +1921,10 @@ public class StorageImplTest {
   @Test
   public void testUpdateDefaultBucketAcl() {
     Acl returnedAcl = ACL.toBuilder().setEtag("ETAG").setId("ID").build();
-    EasyMock.expect(storageRpcMock.patchDefaultAcl(ACL.toObjectPb().setBucket(BUCKET_NAME1)))
-        .andReturn(returnedAcl.toObjectPb());
+    EasyMock.expect(
+            storageRpcMock.patchDefaultAcl(
+                Conversions.apiary().objectAcl().encode(ACL).setBucket(BUCKET_NAME1)))
+        .andReturn(Conversions.apiary().objectAcl().encode(returnedAcl));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Acl acl = storage.updateDefaultAcl(BUCKET_NAME1, ACL);
@@ -1884,7 +1934,10 @@ public class StorageImplTest {
   @Test
   public void testListDefaultBucketAcl() {
     EasyMock.expect(storageRpcMock.listDefaultAcls(BUCKET_NAME1))
-        .andReturn(ImmutableList.of(ACL.toObjectPb(), OTHER_ACL.toObjectPb()));
+        .andReturn(
+            ImmutableList.of(
+                Conversions.apiary().objectAcl().encode(ACL),
+                Conversions.apiary().objectAcl().encode(OTHER_ACL)));
     EasyMock.replay(storageRpcMock);
     initializeService();
     List<Acl> acls = storage.listDefaultAcls(BUCKET_NAME1);
@@ -1895,7 +1948,7 @@ public class StorageImplTest {
   public void testGetBlobAcl() {
     BlobId blobId = BlobId.of(BUCKET_NAME1, BLOB_NAME1, 42L);
     EasyMock.expect(storageRpcMock.getAcl(BUCKET_NAME1, BLOB_NAME1, 42L, "allAuthenticatedUsers"))
-        .andReturn(ACL.toObjectPb());
+        .andReturn(Conversions.apiary().objectAcl().encode(ACL));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Acl acl = storage.getAcl(blobId, User.ofAllAuthenticatedUsers());
@@ -1929,8 +1982,13 @@ public class StorageImplTest {
     Acl returnedAcl = ACL.toBuilder().setEtag("ETAG").setId("ID").build();
     EasyMock.expect(
             storageRpcMock.createAcl(
-                ACL.toObjectPb().setBucket(BUCKET_NAME1).setObject(BLOB_NAME1).setGeneration(42L)))
-        .andReturn(returnedAcl.toObjectPb());
+                Conversions.apiary()
+                    .objectAcl()
+                    .encode(ACL)
+                    .setBucket(BUCKET_NAME1)
+                    .setObject(BLOB_NAME1)
+                    .setGeneration(42L)))
+        .andReturn(Conversions.apiary().objectAcl().encode(returnedAcl));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Acl acl = storage.createAcl(blobId, ACL);
@@ -1943,8 +2001,13 @@ public class StorageImplTest {
     Acl returnedAcl = ACL.toBuilder().setEtag("ETAG").setId("ID").build();
     EasyMock.expect(
             storageRpcMock.patchAcl(
-                ACL.toObjectPb().setBucket(BUCKET_NAME1).setObject(BLOB_NAME1).setGeneration(42L)))
-        .andReturn(returnedAcl.toObjectPb());
+                Conversions.apiary()
+                    .objectAcl()
+                    .encode(ACL)
+                    .setBucket(BUCKET_NAME1)
+                    .setObject(BLOB_NAME1)
+                    .setGeneration(42L)))
+        .andReturn(Conversions.apiary().objectAcl().encode(returnedAcl));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Acl acl = storage.updateAcl(blobId, ACL);
@@ -1955,7 +2018,10 @@ public class StorageImplTest {
   public void testListBlobAcl() {
     BlobId blobId = BlobId.of(BUCKET_NAME1, BLOB_NAME1, 42L);
     EasyMock.expect(storageRpcMock.listAcls(BUCKET_NAME1, BLOB_NAME1, 42L))
-        .andReturn(ImmutableList.of(ACL.toObjectPb(), OTHER_ACL.toObjectPb()));
+        .andReturn(
+            ImmutableList.of(
+                Conversions.apiary().objectAcl().encode(ACL),
+                Conversions.apiary().objectAcl().encode(OTHER_ACL)));
     EasyMock.replay(storageRpcMock);
     initializeService();
     List<Acl> acls = storage.listAcls(blobId);
@@ -2076,8 +2142,9 @@ public class StorageImplTest {
   public void testLockRetentionPolicy() {
     EasyMock.expect(
             storageRpcMock.lockRetentionPolicy(
-                BUCKET_INFO3.toPb(), BUCKET_TARGET_OPTIONS_LOCK_RETENTION_POLICY))
-        .andReturn(BUCKET_INFO3.toPb());
+                Conversions.apiary().bucketInfo().encode(BUCKET_INFO3),
+                BUCKET_TARGET_OPTIONS_LOCK_RETENTION_POLICY))
+        .andReturn(Conversions.apiary().bucketInfo().encode(BUCKET_INFO3));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Bucket bucket =
@@ -2089,7 +2156,7 @@ public class StorageImplTest {
   @Test
   public void testGetServiceAccount() {
     EasyMock.expect(storageRpcMock.getServiceAccount("projectId"))
-        .andReturn(SERVICE_ACCOUNT.toPb());
+        .andReturn(Conversions.apiary().serviceAccount().encode(SERVICE_ACCOUNT));
     EasyMock.replay(storageRpcMock);
     initializeService();
     ServiceAccount serviceAccount = storage.getServiceAccount("projectId");
@@ -2099,9 +2166,10 @@ public class StorageImplTest {
   @Test
   public void testRetryableException() {
     BlobId blob = BlobId.of(BUCKET_NAME1, BLOB_NAME1);
-    EasyMock.expect(storageRpcMock.get(blob.toPb(), EMPTY_RPC_OPTIONS))
+    EasyMock.expect(
+            storageRpcMock.get(Conversions.apiary().blobId().encode(blob), EMPTY_RPC_OPTIONS))
         .andThrow(new StorageException(500, "internalError"))
-        .andReturn(BLOB_INFO1.toPb());
+        .andReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO1));
     EasyMock.replay(storageRpcMock);
     storage =
         options
@@ -2118,7 +2186,8 @@ public class StorageImplTest {
   public void testNonRetryableException() {
     BlobId blob = BlobId.of(BUCKET_NAME1, BLOB_NAME1);
     String exceptionMessage = "Not Implemented";
-    EasyMock.expect(storageRpcMock.get(blob.toPb(), EMPTY_RPC_OPTIONS))
+    EasyMock.expect(
+            storageRpcMock.get(Conversions.apiary().blobId().encode(blob), EMPTY_RPC_OPTIONS))
         .andThrow(new StorageException(501, exceptionMessage));
     EasyMock.replay(storageRpcMock);
     storage =
@@ -2140,7 +2209,8 @@ public class StorageImplTest {
   public void testRuntimeException() {
     BlobId blob = BlobId.of(BUCKET_NAME1, BLOB_NAME1);
     String exceptionMessage = "Artificial runtime exception";
-    EasyMock.expect(storageRpcMock.get(blob.toPb(), EMPTY_RPC_OPTIONS))
+    EasyMock.expect(
+            storageRpcMock.get(Conversions.apiary().blobId().encode(blob), EMPTY_RPC_OPTIONS))
         .andThrow(new RuntimeException(exceptionMessage));
     EasyMock.replay(storageRpcMock);
     storage =
@@ -2256,8 +2326,10 @@ public class StorageImplTest {
                             .build())))
             .build();
     EasyMock.expect(
-            storageRpcMock.create(bucketInfo.toPb(), new HashMap<StorageRpc.Option, Object>()))
-        .andReturn(bucketInfo.toPb());
+            storageRpcMock.create(
+                Conversions.apiary().bucketInfo().encode(bucketInfo),
+                new HashMap<StorageRpc.Option, Object>()))
+        .andReturn(Conversions.apiary().bucketInfo().encode(bucketInfo));
     EasyMock.replay(storageRpcMock);
     initializeService();
     Bucket bucket = storage.create(bucketInfo);