diff --git a/pom.xml b/pom.xml
index a61c695cb201a0c2e026602af9233a204b449420..410704e7b097c36eb3b6c599801e19c2ca191865 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>org.scijava</groupId>
 		<artifactId>pom-scijava</artifactId>
-		<version>26.0.0</version>
+		<version>28.0.0</version>
 		<relativePath />
 	</parent>
 
@@ -87,8 +87,10 @@
 		<stringtemplate.version>3.2.1</stringtemplate.version>
 
 		<!-- TODO: Remove these version pins and update the code. -->
-		<bigdataviewer-core.version>3.0.3</bigdataviewer-core.version>
-		<imglib2.version>3.3.0</imglib2.version>
+		<bigdataviewer-core.version>9.0.3</bigdataviewer-core.version>
+		<imglib2.version>5.9.0</imglib2.version>
+		<imglib2-cache.version>1.0.0-beta-13</imglib2-cache.version>
+		<spim_data.version>2.2.4</spim_data.version>
 	</properties>
 
 	<repositories>
diff --git a/src/main/java/bdv/server/CellHandler.java b/src/main/java/bdv/server/CellHandler.java
index 1cb8682b2b339e9bfbf79896e1582f7c478cab24..9777bf3bd1b9185ab14536897b517c6443f6edaf 100644
--- a/src/main/java/bdv/server/CellHandler.java
+++ b/src/main/java/bdv/server/CellHandler.java
@@ -10,10 +10,15 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
+import java.util.concurrent.ExecutionException;
 import javax.imageio.ImageIO;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import net.imglib2.cache.CacheLoader;
+import net.imglib2.cache.LoaderCache;
+import net.imglib2.cache.ref.SoftRefLoaderCache;
+import net.imglib2.img.cell.Cell;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.log.Log;
@@ -26,12 +31,7 @@ import org.jdom2.output.XMLOutputter;
 import com.google.gson.GsonBuilder;
 
 import bdv.BigDataViewer;
-import bdv.cache.CacheHints;
-import bdv.cache.LoadingStrategy;
-import bdv.img.cache.VolatileCell;
 import bdv.img.cache.VolatileGlobalCellCache;
-import bdv.img.cache.VolatileGlobalCellCache.Key;
-import bdv.img.cache.VolatileGlobalCellCache.VolatileCellLoader;
 import bdv.img.hdf5.Hdf5ImageLoader;
 import bdv.img.hdf5.Hdf5VolatileShortArrayLoader;
 import bdv.img.remote.AffineTransform3DJsonSerializer;
@@ -49,11 +49,74 @@ public class CellHandler extends ContextHandler
 {
 	private static final org.eclipse.jetty.util.log.Logger LOG = Log.getLogger( CellHandler.class );
 
-	private final VolatileGlobalCellCache cache;
+	/**
+	 * Key for a cell identified by timepoint, setup, level, and index
+	 * (flattened spatial coordinate).
+	 */
+	public static class Key
+	{
+		private final int timepoint;
+
+		private final int setup;
+
+		private final int level;
+
+		private final long index;
+
+		private final String[] parts;
+
+		/**
+		 * Create a Key for the specified cell. Note that {@code cellDims} and
+		 * {@code cellMin} are not used for {@code hashcode()/equals()}.
+		 *
+		 * @param timepoint
+		 *            timepoint coordinate of the cell
+		 * @param setup
+		 *            setup coordinate of the cell
+		 * @param level
+		 *            level coordinate of the cell
+		 * @param index
+		 *            index of the cell (flattened spatial coordinate of the
+		 *            cell)
+		 */
+		public Key( final int timepoint, final int setup, final int level, final long index, final String[] parts )
+		{
+			this.timepoint = timepoint;
+			this.setup = setup;
+			this.level = level;
+			this.index = index;
+			this.parts = parts;
+
+			int value = Long.hashCode( index );
+			value = 31 * value + level;
+			value = 31 * value + setup;
+			value = 31 * value + timepoint;
+			hashcode = value;
+		}
+
+		@Override
+		public boolean equals( final Object other )
+		{
+			if ( this == other )
+				return true;
+			if ( !( other instanceof VolatileGlobalCellCache.Key ) )
+				return false;
+			final Key that = ( Key ) other;
+			return ( this.index == that.index ) && ( this.timepoint == that.timepoint ) && ( this.setup == that.setup ) && ( this.level == that.level );
+		}
 
-	private final Hdf5VolatileShortArrayLoader loader;
+		final int hashcode;
 
-	private final CacheHints cacheHints;
+		@Override
+		public int hashCode()
+		{
+			return hashcode;
+		}
+	}
+
+	private final CacheLoader< Key, Cell< ? > > loader;
+
+	private final LoaderCache< Key, Cell< ? > > cache;
 
 	/**
 	 * Full path of the dataset xml file this {@link CellHandler} is serving.
@@ -98,9 +161,20 @@ public class CellHandler extends ContextHandler
 		final SequenceDescriptionMinimal seq = spimData.getSequenceDescription();
 		final Hdf5ImageLoader imgLoader = ( Hdf5ImageLoader ) seq.getImgLoader();
 
-		cache = imgLoader.getCacheControl();
-		loader = imgLoader.getShortArrayLoader();
-		cacheHints = new CacheHints( LoadingStrategy.BLOCKING, 0, false );
+		final Hdf5VolatileShortArrayLoader cacheArrayLoader = imgLoader.getShortArrayLoader();
+		loader = key -> {
+			final int[] cellDims = new int[] {
+					Integer.parseInt( key.parts[ 5 ] ),
+					Integer.parseInt( key.parts[ 6 ] ),
+					Integer.parseInt( key.parts[ 7 ] ) };
+			final long[] cellMin = new long[] {
+					Long.parseLong( key.parts[ 8 ] ),
+					Long.parseLong( key.parts[ 9 ] ),
+					Long.parseLong( key.parts[ 10 ] ) };
+			return new Cell<>( cellDims, cellMin, cacheArrayLoader.loadArray( key.timepoint, key.setup, key.level, cellDims, cellMin ) );
+		};
+
+		cache = new SoftRefLoaderCache<>();
 
 		// dataSetURL property is used for providing the XML file by replace
 		// SequenceDescription>ImageLoader>baseUrl
@@ -145,23 +219,18 @@ public class CellHandler extends ContextHandler
 			final int timepoint = Integer.parseInt( parts[ 2 ] );
 			final int setup = Integer.parseInt( parts[ 3 ] );
 			final int level = Integer.parseInt( parts[ 4 ] );
-			final Key key = new VolatileGlobalCellCache.Key( timepoint, setup, level, index );
-			VolatileCell< ? > cell = cache.getLoadingVolatileCache().getIfPresent( key, cacheHints );
-			if ( cell == null )
+			final Key key = new Key( timepoint, setup, level, index, parts );
+			short[] data;
+			try
+			{
+				final Cell< ? > cell = cache.get( key, loader );
+				data = ( ( VolatileShortArray ) cell.getData() ).getCurrentStorageArray();
+			}
+			catch ( ExecutionException e )
 			{
-				final int[] cellDims = new int[] {
-						Integer.parseInt( parts[ 5 ] ),
-						Integer.parseInt( parts[ 6 ] ),
-						Integer.parseInt( parts[ 7 ] ) };
-				final long[] cellMin = new long[] {
-						Long.parseLong( parts[ 8 ] ),
-						Long.parseLong( parts[ 9 ] ),
-						Long.parseLong( parts[ 10 ] ) };
-				cell = cache.getLoadingVolatileCache().get( key, cacheHints, new VolatileCellLoader<>( loader, timepoint, setup, level, cellDims, cellMin ) );
+				data = new short[ 0 ];
 			}
 
-			@SuppressWarnings( "unchecked" )
-			final short[] data = ( ( VolatileCell< VolatileShortArray > ) cell ).getData().getCurrentStorageArray();
 			final byte[] buf = new byte[ 2 * data.length ];
 			for ( int i = 0, j = 0; i < data.length; i++ )
 			{
diff --git a/src/main/java/bdv/util/ThumbnailGenerator.java b/src/main/java/bdv/util/ThumbnailGenerator.java
index 92c0f322bce7e29f9406fd1d063dba49b65e25f5..f8d64a61efc32e9f4be13b7bee888a998077ea9a 100644
--- a/src/main/java/bdv/util/ThumbnailGenerator.java
+++ b/src/main/java/bdv/util/ThumbnailGenerator.java
@@ -114,7 +114,7 @@ public class ThumbnailGenerator
 		final int numGroups = 10;
 		final ArrayList< SourceGroup > groups = new ArrayList< SourceGroup >( numGroups );
 		for ( int i = 0; i < numGroups; ++i )
-			groups.add( new SourceGroup( "", null ) );
+			groups.add( new SourceGroup( "" ) );
 
 		state = new ViewerState( sources, groups, numTimePoints );
 		if ( !sources.isEmpty() )