diff --git a/java/src/main/java/com/google/crypto/tink/KeyTypeManager.java b/java/src/main/java/com/google/crypto/tink/KeyTypeManager.java
index 934ca845168f58438fe25313e902095062b76d92..17c28748dfc821872ee2fdebfcf40634450b5a95 100644
--- a/java/src/main/java/com/google/crypto/tink/KeyTypeManager.java
+++ b/java/src/main/java/com/google/crypto/tink/KeyTypeManager.java
@@ -173,7 +173,7 @@ public abstract class KeyTypeManager<KeyProtoT extends MessageLite> {
    * be able to generate keys. In particular, in this case it needs to have some KeyFormat protocol
    * buffer which can be validated, parsed, and from which a key can be generated.
    */
-  protected abstract static class KeyFactory<KeyFormatProtoT extends MessageLite, KeyT> {
+  public abstract static class KeyFactory<KeyFormatProtoT extends MessageLite, KeyT> {
     private final Class<KeyFormatProtoT> clazz;
     public KeyFactory(Class<KeyFormatProtoT> clazz) {
       this.clazz = clazz;
diff --git a/java/src/main/java/com/google/crypto/tink/aead/AeadConfig.java b/java/src/main/java/com/google/crypto/tink/aead/AeadConfig.java
index 44200496ea0a6232d8910d1b3c041072596c99e0..58e3450b00baeef72100bdd046129262d1b58cf1 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/AeadConfig.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/AeadConfig.java
@@ -35,7 +35,7 @@ import java.security.GeneralSecurityException;
  */
 public final class AeadConfig {
   public static final String AES_CTR_HMAC_AEAD_TYPE_URL = AesCtrHmacAeadKeyManager.TYPE_URL;
-  public static final String AES_GCM_TYPE_URL = AesGcmKeyManager.TYPE_URL;
+  public static final String AES_GCM_TYPE_URL = new AesGcmKeyManager().getKeyType();
   public static final String AES_EAX_TYPE_URL = AesEaxKeyManager.TYPE_URL;
   public static final String KMS_AEAD_TYPE_URL = KmsAeadKeyManager.TYPE_URL;
   public static final String KMS_ENVELOPE_AEAD_TYPE_URL = KmsEnvelopeAeadKeyManager.TYPE_URL;
@@ -98,7 +98,7 @@ public final class AeadConfig {
     MacConfig.register();
     Registry.registerKeyManager(new AesCtrHmacAeadKeyManager());
     Registry.registerKeyManager(new AesEaxKeyManager());
-    Registry.registerKeyManager(new AesGcmKeyManager());
+    Registry.registerKeyManager(new AesGcmKeyManager(), true);
     Registry.registerKeyManager(new ChaCha20Poly1305KeyManager());
     Registry.registerKeyManager(new KmsAeadKeyManager());
     Registry.registerKeyManager(new KmsEnvelopeAeadKeyManager());
diff --git a/java/src/main/java/com/google/crypto/tink/aead/AeadKeyTemplates.java b/java/src/main/java/com/google/crypto/tink/aead/AeadKeyTemplates.java
index d9aeb0171bbff81c857d640fd4181178a97d8c67..abb79c10f5c2d16c3f4cd5fce1c101e45d38d8eb 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/AeadKeyTemplates.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/AeadKeyTemplates.java
@@ -160,7 +160,7 @@ public final class AeadKeyTemplates {
         .build();
     return KeyTemplate.newBuilder()
         .setValue(format.toByteString())
-        .setTypeUrl(AesGcmKeyManager.TYPE_URL)
+        .setTypeUrl(new AesGcmKeyManager().getKeyType())
         .setOutputPrefixType(OutputPrefixType.TINK)
         .build();
   }
diff --git a/java/src/main/java/com/google/crypto/tink/aead/AesGcmKeyManager.java b/java/src/main/java/com/google/crypto/tink/aead/AesGcmKeyManager.java
index c7aa4f3ebc5c3d2853836b0b120f541ca1d1d75a..0374bea77a315fdd491061a479118f42803bdf95 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/AesGcmKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/AesGcmKeyManager.java
@@ -17,7 +17,7 @@
 package com.google.crypto.tink.aead;
 
 import com.google.crypto.tink.Aead;
-import com.google.crypto.tink.KeyManagerBase;
+import com.google.crypto.tink.KeyTypeManager;
 import com.google.crypto.tink.proto.AesGcmKey;
 import com.google.crypto.tink.proto.AesGcmKeyFormat;
 import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
@@ -32,27 +32,23 @@ import java.security.GeneralSecurityException;
  * This key manager generates new {@code AesGcmKey} keys and produces new instances of {@code
  * AesGcmJce}.
  */
-class AesGcmKeyManager extends KeyManagerBase<Aead, AesGcmKey, AesGcmKeyFormat> {
+class AesGcmKeyManager extends KeyTypeManager<AesGcmKey> {
   public AesGcmKeyManager() {
-    super(Aead.class, AesGcmKey.class, AesGcmKeyFormat.class, TYPE_URL);
+    super(
+        AesGcmKey.class,
+        new PrimitiveFactory<Aead, AesGcmKey>(Aead.class) {
+          @Override
+          public Aead getPrimitive(AesGcmKey key) throws GeneralSecurityException {
+            return new AesGcmJce(key.getKeyValue().toByteArray());
+          }
+        });
   }
 
   private static final int VERSION = 0;
 
-  public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.AesGcmKey";
-
-  /** @param key {@code AesGcmKey} proto */
-  @Override
-  protected Aead getPrimitiveFromKey(AesGcmKey key) throws GeneralSecurityException {
-    return new AesGcmJce(key.getKeyValue().toByteArray());
-  }
-
   @Override
-  protected AesGcmKey newKeyFromFormat(AesGcmKeyFormat format) throws GeneralSecurityException {
-    return AesGcmKey.newBuilder()
-        .setKeyValue(ByteString.copyFrom(Random.randBytes(format.getKeySize())))
-        .setVersion(VERSION)
-        .build();
+  public String getKeyType() {
+    return "type.googleapis.com/google.crypto.tink.AesGcmKey";
   }
 
   @Override
@@ -61,30 +57,42 @@ class AesGcmKeyManager extends KeyManagerBase<Aead, AesGcmKey, AesGcmKeyFormat>
   }
 
   @Override
-  protected KeyMaterialType keyMaterialType() {
+  public KeyMaterialType keyMaterialType() {
     return KeyMaterialType.SYMMETRIC;
   }
 
   @Override
-  protected AesGcmKey parseKeyProto(ByteString byteString)
-      throws InvalidProtocolBufferException {
-    return AesGcmKey.parseFrom(byteString);
+  public void validateKey(AesGcmKey key) throws GeneralSecurityException {
+    Validators.validateVersion(key.getVersion(), getVersion());
+    Validators.validateAesKeySize(key.getKeyValue().size());
   }
 
   @Override
-  protected AesGcmKeyFormat parseKeyFormatProto(ByteString byteString)
-      throws InvalidProtocolBufferException {
-    return AesGcmKeyFormat.parseFrom(byteString);
+  public AesGcmKey parseKey(ByteString byteString) throws InvalidProtocolBufferException {
+    return AesGcmKey.parseFrom(byteString);
   }
 
   @Override
-  protected void validateKey(AesGcmKey key) throws GeneralSecurityException {
-    Validators.validateVersion(key.getVersion(), VERSION);
-    Validators.validateAesKeySize(key.getKeyValue().size());
-  }
+  public KeyFactory<AesGcmKeyFormat, AesGcmKey> keyFactory() {
+    return new KeyFactory<AesGcmKeyFormat, AesGcmKey>(AesGcmKeyFormat.class) {
+      @Override
+      public void validateKeyFormat(AesGcmKeyFormat format) throws GeneralSecurityException {
+        Validators.validateAesKeySize(format.getKeySize());
+      }
 
-  @Override
-  protected void validateKeyFormat(AesGcmKeyFormat format) throws GeneralSecurityException {
-    Validators.validateAesKeySize(format.getKeySize());
+      @Override
+      public AesGcmKeyFormat parseKeyFormat(ByteString byteString)
+          throws InvalidProtocolBufferException {
+        return AesGcmKeyFormat.parseFrom(byteString);
+      }
+
+      @Override
+      public AesGcmKey createKey(AesGcmKeyFormat format) throws GeneralSecurityException {
+        return AesGcmKey.newBuilder()
+            .setKeyValue(ByteString.copyFrom(Random.randBytes(format.getKeySize())))
+            .setVersion(VERSION)
+            .build();
+      }
+    };
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/aead/AeadKeyTemplatesTest.java b/java/src/test/java/com/google/crypto/tink/aead/AeadKeyTemplatesTest.java
index 8cc63831f0f850611142899439151bd517bccd41..b1813a8fe7981d76415719424c1dc940898cda52 100644
--- a/java/src/test/java/com/google/crypto/tink/aead/AeadKeyTemplatesTest.java
+++ b/java/src/test/java/com/google/crypto/tink/aead/AeadKeyTemplatesTest.java
@@ -37,7 +37,7 @@ public class AeadKeyTemplatesTest {
   @Test
   public void testAES128_GCM() throws Exception {
     KeyTemplate template = AeadKeyTemplates.AES128_GCM;
-    assertEquals(AesGcmKeyManager.TYPE_URL, template.getTypeUrl());
+    assertEquals(new AesGcmKeyManager().getKeyType(), template.getTypeUrl());
     assertEquals(OutputPrefixType.TINK, template.getOutputPrefixType());
     AesGcmKeyFormat format = AesGcmKeyFormat.parseFrom(template.getValue());
     assertEquals(16, format.getKeySize());
@@ -46,7 +46,7 @@ public class AeadKeyTemplatesTest {
   @Test
   public void testAES256_GCM() throws Exception {
     KeyTemplate template = AeadKeyTemplates.AES256_GCM;
-    assertEquals(AesGcmKeyManager.TYPE_URL, template.getTypeUrl());
+    assertEquals(new AesGcmKeyManager().getKeyType(), template.getTypeUrl());
     assertEquals(OutputPrefixType.TINK, template.getOutputPrefixType());
     AesGcmKeyFormat format = AesGcmKeyFormat.parseFrom(template.getValue());
     assertEquals(32, format.getKeySize());
@@ -58,7 +58,7 @@ public class AeadKeyTemplatesTest {
     // to test that the function correctly puts them in the resulting template.
     int keySize = 42;
     KeyTemplate template = AeadKeyTemplates.createAesGcmKeyTemplate(keySize);
-    assertEquals(AesGcmKeyManager.TYPE_URL, template.getTypeUrl());
+    assertEquals(new AesGcmKeyManager().getKeyType(), template.getTypeUrl());
     assertEquals(OutputPrefixType.TINK, template.getOutputPrefixType());
 
     AesGcmKeyFormat format = AesGcmKeyFormat.parseFrom(template.getValue());
diff --git a/java/src/test/java/com/google/crypto/tink/aead/AesGcmKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/aead/AesGcmKeyManagerTest.java
index 407a0d27bc9858a3d985a023690a1ae2f01b8908..42212436ea982c365b492fbe73fb5fd4e32a6060 100644
--- a/java/src/test/java/com/google/crypto/tink/aead/AesGcmKeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/aead/AesGcmKeyManagerTest.java
@@ -16,27 +16,20 @@
 
 package com.google.crypto.tink.aead;
 
+import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import com.google.crypto.tink.Aead;
-import com.google.crypto.tink.CryptoFormat;
-import com.google.crypto.tink.KeysetHandle;
 import com.google.crypto.tink.TestUtil;
 import com.google.crypto.tink.proto.AesGcmKey;
 import com.google.crypto.tink.proto.AesGcmKeyFormat;
-import com.google.crypto.tink.proto.KeyData;
-import com.google.crypto.tink.proto.KeyStatusType;
-import com.google.crypto.tink.proto.KeyTemplate;
-import com.google.crypto.tink.proto.OutputPrefixType;
 import com.google.crypto.tink.subtle.Bytes;
-import com.google.crypto.tink.subtle.Random;
 import com.google.protobuf.ByteString;
 import java.security.GeneralSecurityException;
 import java.util.Set;
 import java.util.TreeSet;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -44,62 +37,97 @@ import org.junit.runners.JUnit4;
 /** Test for AesGcmJce and its key manager. */
 @RunWith(JUnit4.class)
 public class AesGcmKeyManagerTest {
-  @BeforeClass
-  public static void setUp() throws GeneralSecurityException {
-    AeadConfig.register();
+  @Test
+  public void validateKeyFormat_empty() throws Exception {
+    try {
+      new AesGcmKeyManager().keyFactory().validateKeyFormat(AesGcmKeyFormat.getDefaultInstance());
+      fail();
+    } catch (GeneralSecurityException e) {
+      // expected.
+    }
   }
 
   @Test
-  public void testNewKeyMultipleTimes() throws Exception {
-    AesGcmKeyFormat gcmKeyFormat =
-        AesGcmKeyFormat.newBuilder()
-            .setKeySize(16)
-            .build();
-    ByteString serialized = ByteString.copyFrom(gcmKeyFormat.toByteArray());
-    KeyTemplate keyTemplate =
-        KeyTemplate.newBuilder().setTypeUrl(AesGcmKeyManager.TYPE_URL).setValue(serialized).build();
-    AesGcmKeyManager keyManager = new AesGcmKeyManager();
-    Set<String> keys = new TreeSet<String>();
-    // Calls newKey multiple times and make sure that they generate different keys.
-    int numTests = 27;
-    for (int i = 0; i < numTests / 3; i++) {
-      AesGcmKey key = (AesGcmKey) keyManager.newKey(gcmKeyFormat);
-      keys.add(TestUtil.hexEncode(key.getKeyValue().toByteArray()));
-      assertEquals(16, key.getKeyValue().toByteArray().length);
-
-      key = (AesGcmKey) keyManager.newKey(serialized);
-      keys.add(TestUtil.hexEncode(key.getKeyValue().toByteArray()));
-      assertEquals(16, key.getKeyValue().toByteArray().length);
-
-      KeyData keyData = keyManager.newKeyData(keyTemplate.getValue());
-      key = AesGcmKey.parseFrom(keyData.getValue());
-      keys.add(TestUtil.hexEncode(key.getKeyValue().toByteArray()));
-      assertEquals(16, key.getKeyValue().toByteArray().length);
-    }
-    assertEquals(numTests, keys.size());
+  public void validateKeyFormat_valid() throws Exception {
+    AesGcmKeyManager manager = new AesGcmKeyManager();
+    manager.keyFactory().validateKeyFormat(AesGcmKeyFormat.newBuilder().setKeySize(16).build());
+    manager.keyFactory().validateKeyFormat(AesGcmKeyFormat.newBuilder().setKeySize(32).build());
   }
 
   @Test
-  public void testNewKeyWithCorruptedFormat() throws Exception {
-    ByteString serialized = ByteString.copyFrom(new byte[128]);
-    KeyTemplate keyTemplate =
-        KeyTemplate.newBuilder().setTypeUrl(AesGcmKeyManager.TYPE_URL).setValue(serialized).build();
-    AesGcmKeyManager keyManager = new AesGcmKeyManager();
+  public void validateKeyFormat_invalid() throws Exception {
+    AesGcmKeyManager.KeyFactory<AesGcmKeyFormat, AesGcmKey> factory =
+        new AesGcmKeyManager().keyFactory();
+    try {
+      factory.validateKeyFormat(AesGcmKeyFormat.newBuilder().setKeySize(1).build());
+      fail();
+    } catch (GeneralSecurityException e) {
+      // expected.
+    }
+    try {
+      factory.validateKeyFormat(AesGcmKeyFormat.newBuilder().setKeySize(15).build());
+      fail();
+    } catch (GeneralSecurityException e) {
+      // expected.
+    }
+    try {
+      factory.validateKeyFormat(AesGcmKeyFormat.newBuilder().setKeySize(17).build());
+      fail();
+    } catch (GeneralSecurityException e) {
+      // expected.
+    }
+    try {
+      factory.validateKeyFormat(AesGcmKeyFormat.newBuilder().setKeySize(31).build());
+      fail();
+    } catch (GeneralSecurityException e) {
+      // expected.
+    }
     try {
-      keyManager.newKey(serialized);
-      fail("Corrupted format, should have thrown exception");
-    } catch (GeneralSecurityException expected) {
-      // Expected
+      factory.validateKeyFormat(AesGcmKeyFormat.newBuilder().setKeySize(33).build());
+      fail();
+    } catch (GeneralSecurityException e) {
+      // expected.
     }
     try {
-      keyManager.newKeyData(keyTemplate.getValue());
-      fail("Corrupted format, should have thrown exception");
-    } catch (GeneralSecurityException expected) {
-      // Expected
+      factory.validateKeyFormat(AesGcmKeyFormat.newBuilder().setKeySize(64).build());
+      fail();
+    } catch (GeneralSecurityException e) {
+      // expected.
     }
   }
 
-  private static final int AES_KEY_SIZE = 16;
+  @Test
+  public void createKey_16Bytes() throws Exception {
+    AesGcmKey key =
+        new AesGcmKeyManager()
+            .keyFactory()
+            .createKey(AesGcmKeyFormat.newBuilder().setKeySize(16).build());
+    assertThat(key.getKeyValue()).hasSize(16);
+  }
+
+  @Test
+  public void createKey_32Bytes() throws Exception {
+    AesGcmKey key =
+        new AesGcmKeyManager()
+            .keyFactory()
+            .createKey(AesGcmKeyFormat.newBuilder().setKeySize(32).build());
+    assertThat(key.getKeyValue()).hasSize(32);
+  }
+
+  @Test
+  public void createKey_multipleTimes() throws Exception {
+    AesGcmKeyFormat gcmKeyFormat = AesGcmKeyFormat.newBuilder().setKeySize(16).build();
+
+    AesGcmKeyManager keyManager = new AesGcmKeyManager();
+    Set<String> keys = new TreeSet<>();
+    // Calls newKey multiple times and make sure that they generate different keys.
+    int numTests = 50;
+    for (int i = 0; i < numTests; i++) {
+      AesGcmKey key = keyManager.keyFactory().createKey(gcmKeyFormat);
+      keys.add(TestUtil.hexEncode(key.getKeyValue().toByteArray()));
+    }
+    assertEquals(numTests, keys.size());
+  }
 
   private static class NistTestVector {
     String name;
@@ -297,7 +325,8 @@ public class AesGcmKeyManagerTest {
         // We support only 12-byte IV and 16-byte tag.
         continue;
       }
-      Aead aead = getRawAesGcm(t.keyValue);
+      AesGcmKey key = AesGcmKey.newBuilder().setKeyValue(ByteString.copyFrom(t.keyValue)).build();
+      Aead aead = new AesGcmKeyManager().getPrimitive(key, Aead.class);
       try {
         byte[] ciphertext = Bytes.concat(t.iv, t.ciphertext, t.tag);
         byte[] plaintext = aead.decrypt(ciphertext, t.aad);
@@ -308,49 +337,17 @@ public class AesGcmKeyManagerTest {
     }
   }
 
-  private Aead getRawAesGcm(byte[] keyValue) throws Exception {
-    KeysetHandle keysetHandle =
-        TestUtil.createKeysetHandle(
-            TestUtil.createKeyset(
-                TestUtil.createKey(
-                    TestUtil.createAesGcmKeyData(keyValue),
-                    42,
-                    KeyStatusType.ENABLED,
-                    OutputPrefixType.RAW)));
-    return keysetHandle.getPrimitive(Aead.class);
-  }
-
-  @Test
-  public void testBasic() throws Exception {
-    byte[] keyValue = Random.randBytes(AES_KEY_SIZE);
-    KeysetHandle keysetHandle =
-        TestUtil.createKeysetHandle(
-            TestUtil.createKeyset(
-                TestUtil.createKey(
-                    TestUtil.createAesGcmKeyData(keyValue),
-                    42,
-                    KeyStatusType.ENABLED,
-                    OutputPrefixType.TINK)));
-    TestUtil.runBasicAeadTests(keysetHandle.getPrimitive(Aead.class));
-  }
-
   @Test
   public void testCiphertextSize() throws Exception {
-    byte[] keyValue = Random.randBytes(AES_KEY_SIZE);
-    KeysetHandle keysetHandle =
-        TestUtil.createKeysetHandle(
-            TestUtil.createKeyset(
-                TestUtil.createKey(
-                    TestUtil.createAesGcmKeyData(keyValue),
-                    42,
-                    KeyStatusType.ENABLED,
-                    OutputPrefixType.TINK)));
-    Aead aead = keysetHandle.getPrimitive(Aead.class);
+    AesGcmKey key =
+        new AesGcmKeyManager()
+            .keyFactory()
+            .createKey(AesGcmKeyFormat.newBuilder().setKeySize(32).build());
+    Aead aead = new AesGcmKeyManager().getPrimitive(key, Aead.class);
     byte[] plaintext = "plaintext".getBytes("UTF-8");
     byte[] associatedData = "associatedData".getBytes("UTF-8");
     byte[] ciphertext = aead.encrypt(plaintext, associatedData);
-    assertEquals(
-        CryptoFormat.NON_RAW_PREFIX_SIZE + 12 /* IV_SIZE */ + plaintext.length + 16 /* TAG_SIZE */,
-        ciphertext.length);
+    assertThat(ciphertext.length)
+        .isEqualTo(12 /* IV_SIZE */ + plaintext.length + 16 /* TAG_SIZE */);
   }
 }