StreamStore has inconsistent handling of nonexistent IDs
The `StreamStore` throws an exception if you call `loadStream` with a nonexistent ID. However, the existence check is bypassed when calling `loadStreams`, and the entry is simply omitted from the returned map.

Not sure if this is actually a bug or intended behavior - but IMO it should be consistent (either both throw, or loadStream returns Optional).
diff --git a/atlasdb-client/src/main/java/com/palantir/atlasdb/stream/AbstractGenericStreamStore.java b/atlasdb-client/src/main/java/com/palantir/atlasdb/stream/AbstractGenericStreamStore.java
index 5797b11ed3..a2043bccd7 100644
--- a/atlasdb-client/src/main/java/com/palantir/atlasdb/stream/AbstractGenericStreamStore.java
+++ b/atlasdb-client/src/main/java/com/palantir/atlasdb/stream/AbstractGenericStreamStore.java
@@ -23,16 +23,18 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import javax.annotation.CheckForNull;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import com.google.common.primitives.Ints;
 import com.palantir.atlasdb.protos.generated.StreamPersistence.Status;
 import com.palantir.atlasdb.protos.generated.StreamPersistence.StreamMetadata;
@@ -70,15 +72,24 @@ public abstract class AbstractGenericStreamStore<ID> implements GenericStreamSto
         return getStream(transaction, id, metadata);
     }
 
+    @Override
+    public Optional<InputStream> loadSingleStream(Transaction transaction, final ID id) {
+        Map<ID, StreamMetadata> idToMetadata = getMetadata(transaction, ImmutableSet.of(id));
+        if (idToMetadata.isEmpty()) {
+            return Optional.empty();
+        }
+
+        StreamMetadata metadata = getOnlyStreamMetadata(idToMetadata);
+        return Optional.of(getStream(transaction, id, metadata));
+    }
+
     @Override
     public Map<ID, InputStream> loadStreams(Transaction transaction, Set<ID> ids) {
-        Map<ID, InputStream> ret = Maps.newHashMap();
         Map<ID, StreamMetadata> idsToMetadata = getMetadata(transaction, ids);
-        for (Map.Entry<ID, StreamMetadata> entry : idsToMetadata.entrySet()) {
-            ID id = entry.getKey();
-            ret.put(id, getStream(transaction, id, entry.getValue()));
-        }
-        return ret;
+
+        return idsToMetadata.entrySet().stream()
+                .map(e -> Maps.immutableEntry(e.getKey(), getStream(transaction, e.getKey(), e.getValue())))
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
     }
 
     private InputStream getStream(Transaction transaction, ID id, StreamMetadata metadata) {
@@ -213,6 +224,10 @@ public abstract class AbstractGenericStreamStore<ID> implements GenericStreamSto
     protected abstract Map<ID, StreamMetadata> getMetadata(Transaction tx, Set<ID> streamIds);
 
     private StreamMetadata getMetadata(Transaction transaction, ID id) {
-        return Iterables.getOnlyElement(getMetadata(transaction, Sets.newHashSet(id)).entrySet()).getValue();
+        return getOnlyStreamMetadata(getMetadata(transaction, ImmutableSet.of(id)));
+    }
+
+    private StreamMetadata getOnlyStreamMetadata(Map<ID, StreamMetadata> idToMetadata) {
+        return Iterables.getOnlyElement(idToMetadata.values());
     }
 }
diff --git a/atlasdb-client/src/main/java/com/palantir/atlasdb/stream/GenericStreamStore.java b/atlasdb-client/src/main/java/com/palantir/atlasdb/stream/GenericStreamStore.java
index 12a935fd15..36fcd133ed 100644
--- a/atlasdb-client/src/main/java/com/palantir/atlasdb/stream/GenericStreamStore.java
+++ b/atlasdb-client/src/main/java/com/palantir/atlasdb/stream/GenericStreamStore.java
@@ -18,6 +18,7 @@ package com.palantir.atlasdb.stream;
 import java.io.File;
 import java.io.InputStream;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 import com.palantir.atlasdb.transaction.api.Transaction;
@@ -27,11 +28,35 @@ import com.palantir.util.crypto.Sha256Hash;
  * Interface for storing streams specifically for atlasdb.
  */
 public interface GenericStreamStore<ID> {
-    static int BLOCK_SIZE_IN_BYTES = 1000000; // 1MB. DO NOT CHANGE THIS WITHOUT AN UPGRADE TASK
+    int BLOCK_SIZE_IN_BYTES = 1000000; // 1MB. DO NOT CHANGE THIS WITHOUT AN UPGRADE TASK
 
+    /**
+     * Implemented in the generated StreamStore implementation.
+     * Use this to look up stream IDs, given a hash of the corresponding InputStream's content.
+     */
     Map<Sha256Hash, ID> lookupStreamIdsByHash(Transaction t, final Set<Sha256Hash> hashes);
 
+    /**
+     * @deprecated use #loadSingleStream instead.
+     *
+     * Returns an InputStream if such a stream exists, throwing an exception if no stream exists.
+     */
+    @Deprecated
     InputStream loadStream(Transaction t, ID id);
+
+    /**
+     * Loads the stream with ID id, returning {@code Optional.empty} if no such stream exists.
+     */
+    Optional<InputStream> loadSingleStream(Transaction t, ID id);
+
+    /**
+     * Loads the streams for each ID in ids.
+     * If an id has no corresponding stream, it will be omitted from the returned map.
+     */
     Map<ID, InputStream> loadStreams(Transaction t, Set<ID> ids);
+
+    /**
+     * Loads the whole stream, and saves it to a local temporary file.
+     */
     File loadStreamAsFile(Transaction t, ID id);
 }
diff --git a/atlasdb-client/src/main/java/com/palantir/atlasdb/table/description/render/StreamStoreRenderer.java b/atlasdb-client/src/main/java/com/palantir/atlasdb/table/description/render/StreamStoreRenderer.java
index 089e2cdd0a..02c92fab32 100644
--- a/atlasdb-client/src/main/java/com/palantir/atlasdb/table/description/render/StreamStoreRenderer.java
+++ b/atlasdb-client/src/main/java/com/palantir/atlasdb/table/description/render/StreamStoreRenderer.java
@@ -30,6 +30,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
@@ -83,6 +84,7 @@ import com.palantir.util.file.TempFileUtils;
 
 import net.jpountz.lz4.LZ4BlockInputStream;
 
+@SuppressWarnings("CheckStyle")
 public class StreamStoreRenderer {
     private final String name;
     private final ValueType streamIdType;
@@ -174,6 +176,8 @@ public class StreamStoreRenderer {
                         line();
                         loadStreamWithCompression();
                         line();
+                        loadSingleStreamWithCompression();
+                        line();
                         loadStreamsWithCompression();
                         line();
                         tryWriteStreamToFile();
@@ -565,6 +569,14 @@ public class StreamStoreRenderer {
                 } line("}");
             }
 
+            private void loadSingleStreamWithCompression() {
+                line("@Override");
+                line("public Optional<InputStream> loadSingleStream(Transaction t, final ", StreamId, " id) {"); {
+                    line("Optional<InputStream> inputStream = super.loadSingleStream(t, id);");
+                    line("return inputStream.map(LZ4BlockInputStream::new);");
+                } line("}");
+            }
+
             private void loadStreamsWithCompression() {
                 line("@Override");
                 line("public Map<", StreamId, ", InputStream> loadStreams(Transaction t, Set<", StreamId, "> ids) {"); {
@@ -756,6 +768,7 @@ public class StreamStoreRenderer {
         DigestInputStream.class,
         MessageDigest.class,
         Collection.class,
+        Optional.class,
         Map.class,
         Entry.class,
         Set.class,
diff --git a/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/StreamTest.java b/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/StreamTest.java
index 8f62136efd..973f619011 100644
--- a/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/StreamTest.java
+++ b/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/StreamTest.java
@@ -15,21 +15,25 @@
  */
 package com.palantir.atlasdb.schema.stream;
 
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.hamcrest.core.IsNot.not;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertNotNull;
-
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Arrays;
 import java.util.Map;
-import java.util.NoSuchElementException;
+import java.util.Optional;
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -38,7 +42,7 @@ import java.util.concurrent.Future;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
-import org.junit.Assert;
+import org.apache.commons.io.output.ByteArrayOutputStream;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -106,27 +110,35 @@ public class StreamTest extends AtlasDbTestCase {
     @Test
     public void testAddDelete() throws Exception {
         final byte[] data = PtBytes.toBytes("streamed");
-        final long streamId = txManager.runTaskWithRetry(new TransactionTask<Long, Exception>() {
-            @Override
-            public Long execute(Transaction t) throws Exception {
-                byte[] data = PtBytes.toBytes("streamed");
-                Sha256Hash hash = Sha256Hash.computeHash(data);
-                byte[] reference = "ref".getBytes();
-                long streamId = defaultStore.getByHashOrStoreStreamAndMarkAsUsed(t, hash, new ByteArrayInputStream(data), reference);
-                try {
-                    defaultStore.loadStream(t, 1L).read(data, 0, data.length);
-                } catch (NoSuchElementException e) {
-                    // expected
-                }
-                return streamId;
-            }
+        final long streamId = txManager.runTaskWithRetry((TransactionTask<Long, Exception>) t -> {
+            Sha256Hash hash = Sha256Hash.computeHash(data);
+            byte[] reference = "ref".getBytes();
+
+            return defaultStore.getByHashOrStoreStreamAndMarkAsUsed(t, hash, new ByteArrayInputStream(data), reference);
         });
-        txManager.runTaskWithRetry(new TransactionTask<Void, Exception>() {
-            @Override
-            public Void execute(Transaction t) throws Exception {
-                Assert.assertEquals(data.length, defaultStore.loadStream(t, streamId).read(data, 0, data.length));
-                return null;
-            }
+        txManager.runTaskWithRetry((TransactionTask<Void, Exception>) t -> {
+            Optional<InputStream> inputStream = defaultStore.loadSingleStream(t, streamId);
+            assertTrue(inputStream.isPresent());
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            outputStream.write(inputStream.get());
+            assertArrayEquals(data, outputStream.toByteArray());
+            return null;
+        });
+    }
+
+    @Test
+    public void testLoadStreamWithWrongId() throws Exception {
+        final byte[] data = PtBytes.toBytes("streamed");
+        long streamId = txManager.runTaskWithRetry((TransactionTask<Long, Exception>) t -> {
+            Sha256Hash hash = Sha256Hash.computeHash(data);
+            byte[] reference = "ref".getBytes();
+
+            return defaultStore.getByHashOrStoreStreamAndMarkAsUsed(t, hash,
+                    new ByteArrayInputStream(data), reference);
+        });
+        txManager.runTaskWithRetry((TransactionTask<Void, Exception>) t -> {
+            assertThat(defaultStore.loadSingleStream(t, streamId ^ 1L), is(Optional.empty()));
+            return null;
         });
     }
 
@@ -136,7 +148,7 @@ public class StreamTest extends AtlasDbTestCase {
         byte[] persistedRow = row.persistToBytes();
         StreamTestWithHashStreamValueRow hydratedRow =
                 StreamTestWithHashStreamValueRow.BYTES_HYDRATOR.hydrateFromBytes(persistedRow);
-        Assert.assertEquals(row, hydratedRow);
+        assertEquals(row, hydratedRow);
     }
 
     @Test
@@ -145,7 +157,7 @@ public class StreamTest extends AtlasDbTestCase {
         byte[] persistedRow = row.persistToBytes();
         StreamTestWithHashStreamMetadataRow hydratedRow =
                 StreamTestWithHashStreamMetadataRow.BYTES_HYDRATOR.hydrateFromBytes(persistedRow);
-        Assert.assertEquals(row, hydratedRow);
+        assertEquals(row, hydratedRow);
     }
 
     @Test
@@ -154,7 +166,7 @@ public class StreamTest extends AtlasDbTestCase {
         byte[] persistedRow = row.persistToBytes();
         StreamTestWithHashStreamIdxRow hydratedRow =
                 StreamTestWithHashStreamIdxRow.BYTES_HYDRATOR.hydrateFromBytes(persistedRow);
-        Assert.assertEquals(row, hydratedRow);
+        assertEquals(row, hydratedRow);
     }
 
     @Test
@@ -179,7 +191,8 @@ public class StreamTest extends AtlasDbTestCase {
 
     @Test
     public void testStoreByteStreamJustAboveInMemoryThreshold() throws IOException {
-        storeAndCheckByteStreams(defaultStore, getIncompressibleBytes(StreamTestStreamStore.BLOCK_SIZE_IN_BYTES * 4 + 1));
+        storeAndCheckByteStreams(defaultStore,
+                getIncompressibleBytes(StreamTestStreamStore.BLOCK_SIZE_IN_BYTES * 4 + 1));
     }
 
     @Test
@@ -260,13 +273,21 @@ public class StreamTest extends AtlasDbTestCase {
 
     private void verifyLoadingStreams(PersistentStreamStore store, long id, byte[] bytesToStore) throws IOException {
         verifyLoadStream(store, id, bytesToStore);
+        verifyLoadSingleStream(store, id, bytesToStore);
         verifyLoadStreams(store, id, bytesToStore);
         verifyLoadStreamAsFile(store, id, bytesToStore);
     }
 
-    private void verifyLoadStreamAsFile(PersistentStreamStore store, long id, byte[] bytesToStore) throws IOException {
-        File file = txManager.runTaskThrowOnConflict(t -> store.loadStreamAsFile(t, id));
-        Assert.assertArrayEquals(bytesToStore, FileUtils.readFileToByteArray(file));
+    @SuppressWarnings("deprecation")
+    private void verifyLoadStream(PersistentStreamStore store, long id, byte[] bytesToStore) throws IOException {
+        InputStream stream = txManager.runTaskThrowOnConflict(t -> store.loadStream(t, id));
+        assertStreamHasBytes(stream, bytesToStore);
+    }
+
+    private void verifyLoadSingleStream(PersistentStreamStore store, long id, byte[] toStore) throws IOException {
+        Optional<InputStream> stream = txManager.runTaskThrowOnConflict(t -> store.loadSingleStream(t, id));
+        assertTrue(stream.isPresent());
+        assertStreamHasBytes(stream.get(), toStore);
     }
 
     private void verifyLoadStreams(PersistentStreamStore store, long id, byte[] bytesToStore) throws IOException {
@@ -275,23 +296,23 @@ public class StreamTest extends AtlasDbTestCase {
         assertStreamHasBytes(streams.get(id), bytesToStore);
     }
 
-    private void verifyLoadStream(PersistentStreamStore store, long id, byte[] bytesToStore) throws IOException {
-        InputStream stream = txManager.runTaskThrowOnConflict(t -> store.loadStream(t, id));
-        assertStreamHasBytes(stream, bytesToStore);
+    private void verifyLoadStreamAsFile(PersistentStreamStore store, long id, byte[] bytesToStore) throws IOException {
+        File file = txManager.runTaskThrowOnConflict(t -> store.loadStreamAsFile(t, id));
+        assertArrayEquals(bytesToStore, FileUtils.readFileToByteArray(file));
     }
 
     private void assertStreamHasBytes(InputStream stream, byte[] bytes) throws IOException {
         byte[] streamAsBytes = IOUtils.toByteArray(stream);
-        Assert.assertArrayEquals(bytes, streamAsBytes);
+        assertArrayEquals(bytes, streamAsBytes);
         stream.close();
     }
 
-    @Test()
+    @Test
     public void readFromStreamWhenTransactionOpen() throws IOException {
         readFromGivenStreamWhenTransactionOpen(defaultStore);
     }
 
-    @Test()
+    @Test
     public void readFromCompressedStreamWhenTransactionOpen() throws IOException {
         readFromGivenStreamWhenTransactionOpen(compressedStore);
     }
@@ -456,11 +477,12 @@ public class StreamTest extends AtlasDbTestCase {
 
         txManager.runTaskWithRetry(t -> defaultStore.storeStreams(t, streams));
 
-        Map<Sha256Hash, Long> sha256HashLongMap = txManager.runTaskWithRetry(t -> defaultStore.lookupStreamIdsByHash(t, ImmutableSet.of(hash1, hash2, hash3)));
+        Map<Sha256Hash, Long> sha256HashLongMap = txManager.runTaskWithRetry(
+                t -> defaultStore.lookupStreamIdsByHash(t, ImmutableSet.of(hash1, hash2, hash3)));
 
         assertEquals(id1, sha256HashLongMap.get(hash1).longValue());
         assertEquals(id2, sha256HashLongMap.get(hash2).longValue());
-        assertEquals(null, sha256HashLongMap.get(hash3));
+        assertNull(sha256HashLongMap.get(hash3));
     }
 
     @Test
@@ -491,16 +513,16 @@ public class StreamTest extends AtlasDbTestCase {
 
         runConflictingTasksConcurrently(streamId, new TwoConflictingTasks() {
             @Override
-            public void startFirstAndFail(Transaction t, long streamId) {
+            public void startFirstAndFail(Transaction tx, long streamId) {
                 StreamTestStreamStore ss = StreamTestStreamStore.of(txManager, StreamTestTableFactory.of());
-                ss.storeStreams(t, ImmutableMap.of(streamId, new ByteArrayInputStream(new byte[1])));
+                ss.storeStreams(tx, ImmutableMap.of(streamId, new ByteArrayInputStream(new byte[1])));
             }
 
             @Override
-            public void startSecondAndFinish(Transaction t, long streamId) {
-                DeletingStreamStore deletingStreamStore =
-                        new DeletingStreamStore(StreamTestStreamStore.of(txManager, StreamTestTableFactory.of()));
-                deletingStreamStore.deleteStreams(t, ImmutableSet.of(streamId));
+            public void startSecondAndFinish(Transaction tx, long streamId) {
+                DeletingStreamStore deletingStreamStore = new DeletingStreamStore(
+                        StreamTestStreamStore.of(txManager, StreamTestTableFactory.of()));
+                deletingStreamStore.deleteStreams(tx, ImmutableSet.of(streamId));
             }
         });
 
@@ -513,19 +535,22 @@ public class StreamTest extends AtlasDbTestCase {
 
         runConflictingTasksConcurrently(streamId, new TwoConflictingTasks() {
             @Override
-            public void startFirstAndFail(Transaction t, long streamId) {
-                DeletingStreamStore deletingStreamStore = new DeletingStreamStore(StreamTestStreamStore.of(txManager, StreamTestTableFactory.of()));
-                deletingStreamStore.deleteStreams(t, ImmutableSet.of(streamId));
+            public void startFirstAndFail(Transaction tx, long streamId) {
+                DeletingStreamStore deletingStreamStore = new DeletingStreamStore(
+                        StreamTestStreamStore.of(txManager, StreamTestTableFactory.of()));
+                deletingStreamStore.deleteStreams(tx, ImmutableSet.of(streamId));
             }
 
             @Override
-            public void startSecondAndFinish(Transaction t, long streamId) {
+            public void startSecondAndFinish(Transaction tx, long streamId) {
                 StreamTestStreamStore ss = StreamTestStreamStore.of(txManager, StreamTestTableFactory.of());
-                ss.storeStreams(t, ImmutableMap.of(streamId, new ByteArrayInputStream(new byte[1])));
+                ss.storeStreams(tx, ImmutableMap.of(streamId, new ByteArrayInputStream(new byte[1])));
             }
         });
 
-        assertNotNull(getStream(streamId));
+        Optional<InputStream> stream = getStream(streamId);
+        assertTrue(stream.isPresent());
+        assertNotNull(stream.get());
     }
 
     @Test
@@ -554,23 +579,18 @@ public class StreamTest extends AtlasDbTestCase {
         return (metadata.getLength() + blockSize - 1) / blockSize;
     }
 
-    private InputStream getStream(long streamId) {
+    private Optional<InputStream> getStream(long streamId) {
         return txManager.runTaskThrowOnConflict(t -> {
             StreamTestStreamStore streamStore = StreamTestStreamStore.of(txManager, StreamTestTableFactory.of());
-            return streamStore.loadStream(t, streamId);
+            return streamStore.loadSingleStream(t, streamId);
         });
     }
 
     private void assertStreamDoesNotExist(final long streamId) {
-        try {
-            getStream(streamId);
-            fail("This element should have been deleted");
-        } catch (NoSuchElementException e) {
-            // expected
-        }
+        assertFalse("This element should have been deleted", getStream(streamId).isPresent());
     }
 
-    private void runConflictingTasksConcurrently(long streamId, TwoConflictingTasks twoConflictingTasks) throws InterruptedException {
+    private void runConflictingTasksConcurrently(long streamId, TwoConflictingTasks tasks) throws InterruptedException {
         final CountDownLatch firstLatch = new CountDownLatch(1);
         final CountDownLatch secondLatch = new CountDownLatch(1);
 
@@ -579,7 +599,7 @@ public class StreamTest extends AtlasDbTestCase {
         Future<?> firstFuture = exec.submit(() -> {
             try {
                 txManager.runTaskThrowOnConflict(t -> {
-                    twoConflictingTasks.startFirstAndFail(t, streamId);
+                    tasks.startFirstAndFail(t, streamId);
                     letOtherTaskFinish(firstLatch, secondLatch);
                     return null;
                 });
@@ -591,10 +611,11 @@ public class StreamTest extends AtlasDbTestCase {
 
         firstLatch.await();
 
-        Future<?> secondFuture = exec.submit((Runnable) () -> txManager.runTaskThrowOnConflict((TransactionTask<Void, RuntimeException>) t -> {
-            twoConflictingTasks.startSecondAndFinish(t, streamId);
-            return null;
-        }));
+        Future<?> secondFuture = exec.submit((Runnable) () ->
+                txManager.runTaskThrowOnConflict((TransactionTask<Void, RuntimeException>) t -> {
+                    tasks.startSecondAndFinish(t, streamId);
+                    return null;
+                }));
 
         exec.shutdown();
         Futures.getUnchecked(secondFuture);
@@ -612,9 +633,9 @@ public class StreamTest extends AtlasDbTestCase {
         }
     }
 
-    abstract class TwoConflictingTasks {
-        public abstract void startFirstAndFail(Transaction t, long streamId);
-        public abstract void startSecondAndFinish(Transaction t, long streamId);
+    private abstract class TwoConflictingTasks {
+        public abstract void startFirstAndFail(Transaction tx, long streamId);
+        public abstract void startSecondAndFinish(Transaction tx, long streamId);
     }
 
     private byte[] getCompressibleBytes(int size) {
diff --git a/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestMaxMemStreamStore.java b/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestMaxMemStreamStore.java
index da0081bf6e..a059460a39 100644
--- a/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestMaxMemStreamStore.java
+++ b/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestMaxMemStreamStore.java
@@ -15,6 +15,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
@@ -413,6 +414,7 @@ public final class StreamTestMaxMemStreamStore extends AbstractPersistentStreamS
      * {@link MessageDigest}
      * {@link Multimap}
      * {@link Multimaps}
+     * {@link Optional}
      * {@link OutputStream}
      * {@link Pair}
      * {@link PersistentStreamStore}
diff --git a/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestStreamStore.java b/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestStreamStore.java
index 1fcd483af2..5e823fb4cf 100644
--- a/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestStreamStore.java
+++ b/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestStreamStore.java
@@ -15,6 +15,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
@@ -413,6 +414,7 @@ public final class StreamTestStreamStore extends AbstractPersistentStreamStore {
      * {@link MessageDigest}
      * {@link Multimap}
      * {@link Multimaps}
+     * {@link Optional}
      * {@link OutputStream}
      * {@link Pair}
      * {@link PersistentStreamStore}
diff --git a/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestWithHashStreamStore.java b/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestWithHashStreamStore.java
index 930ddb6f98..3fe454c3fe 100644
--- a/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestWithHashStreamStore.java
+++ b/atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestWithHashStreamStore.java
@@ -15,6 +15,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
@@ -209,6 +210,12 @@ public final class StreamTestWithHashStreamStore extends AbstractPersistentStrea
         return new LZ4BlockInputStream(super.loadStream(t, id));
     }
 
+    @Override
+    public Optional<InputStream> loadSingleStream(Transaction t, final Long id) {
+        Optional<InputStream> inputStream = super.loadSingleStream(t, id);
+        return inputStream.map(LZ4BlockInputStream::new);
+    }
+
     @Override
     public Map<Long, InputStream> loadStreams(Transaction t, Set<Long> ids) {
         Map<Long, InputStream> compressedStreams = super.loadStreams(t, ids);
@@ -465,6 +472,7 @@ public final class StreamTestWithHashStreamStore extends AbstractPersistentStrea
      * {@link MessageDigest}
      * {@link Multimap}
      * {@link Multimaps}
+     * {@link Optional}
      * {@link OutputStream}
      * {@link Pair}
      * {@link PersistentStreamStore}
diff --git a/docs/source/release_notes/release-notes.rst b/docs/source/release_notes/release-notes.rst
index feac56600f..7e8d99e610 100644
--- a/docs/source/release_notes/release-notes.rst
+++ b/docs/source/release_notes/release-notes.rst
@@ -49,6 +49,11 @@ develop
          - Improved performance of paging over dynamic columns on Oracle DbKvs: the time required to page through a large wide row is now linear rather than quadratic in the length of the row.
            (`Pull Request <https://github.com/palantir/atlasdb/pull/1702>`__)
 
+    *    - |deprecated|
+         - ``GenericStreamStore.loadStream`` has been deprecated. Use ``loadSingleStream``, which returns an
+           ``Optional<InputStream>``, instead.
+           (`Pull Request <https://github.com/palantir/atlasdb/pull/1265>`__)
+
 .. <<<<------------------------------------------------------------------------------------------------------------->>>>
 
 =======
diff --git a/examples/profile-client/build.gradle b/examples/profile-client/build.gradle
index ecbca53543..18e4cdc09c 100644
--- a/examples/profile-client/build.gradle
+++ b/examples/profile-client/build.gradle
@@ -10,4 +10,6 @@ dependencies {
   compile project(":atlasdb-client")
   compile project(":examples:profile-client-protobufs")
   testCompile project(":atlasdb-impl-shared")
+
+  testCompile group: 'org.hamcrest', name: 'hamcrest-library'
 }
diff --git a/examples/profile-client/src/main/java/com/palantir/example/profile/schema/generated/UserPhotosStreamStore.java b/examples/profile-client/src/main/java/com/palantir/example/profile/schema/generated/UserPhotosStreamStore.java
index 6c8fd2a6af..2c13d99595 100644
--- a/examples/profile-client/src/main/java/com/palantir/example/profile/schema/generated/UserPhotosStreamStore.java
+++ b/examples/profile-client/src/main/java/com/palantir/example/profile/schema/generated/UserPhotosStreamStore.java
@@ -15,6 +15,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
@@ -413,6 +414,7 @@ public final class UserPhotosStreamStore extends AbstractPersistentStreamStore {
      * {@link MessageDigest}
      * {@link Multimap}
      * {@link Multimaps}
+     * {@link Optional}
      * {@link OutputStream}
      * {@link Pair}
      * {@link PersistentStreamStore}
diff --git a/examples/profile-client/src/test/java/com/palantir/example/profile/ProfileStoreTest.java b/examples/profile-client/src/test/java/com/palantir/example/profile/ProfileStoreTest.java
index e409db8f2d..c7b67eace0 100644
--- a/examples/profile-client/src/test/java/com/palantir/example/profile/ProfileStoreTest.java
+++ b/examples/profile-client/src/test/java/com/palantir/example/profile/ProfileStoreTest.java
@@ -15,6 +15,9 @@
  */
 package com.palantir.example.profile;
 
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -95,6 +98,16 @@ public class ProfileStoreTest {
         });
     }
 
+    @Test
+    public void testGetImageBehaviourWhenNoImage() {
+        final UUID userId = UUID.randomUUID();
+        runWithRetry(store -> {
+            InputStream image = store.getImageForUser(userId);
+            assertThat(image, nullValue());
+            return null;
+        });
+    }
+
     @Test
     public void testStoreImageThrowsAfterTransactionManagerIsClosedThrows() throws Exception {
         txnMgr.close();
['atlasdb-client/src/main/java/com/palantir/atlasdb/stream/GenericStreamStore.java', 'examples/profile-client/src/test/java/com/palantir/example/profile/ProfileStoreTest.java', 'atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestStreamStore.java', 'atlasdb-client/src/main/java/com/palantir/atlasdb/table/description/render/StreamStoreRenderer.java', 'atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/StreamTest.java', 'docs/source/release_notes/release-notes.rst', 'atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestMaxMemStreamStore.java', 'atlasdb-tests-shared/src/test/java/com/palantir/atlasdb/schema/stream/generated/StreamTestWithHashStreamStore.java', 'atlasdb-client/src/main/java/com/palantir/atlasdb/stream/AbstractGenericStreamStore.java', 'examples/profile-client/build.gradle', 'examples/profile-client/src/main/java/com/palantir/example/profile/schema/generated/UserPhotosStreamStore.java']