diff --git a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java
index 6627a1be4125e944562f4d0ca1b74c65264acaae..067f4e0d726018d27509f6d043db5938b035226b 100644
--- a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java
+++ b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java
@@ -105,13 +105,13 @@ public class BlockfileNamingService extends DummyNamingService {
     private String _version = "0";
     private boolean _needsUpgrade;
 
-    private static final Serializer _infoSerializer = new PropertiesSerializer();
-    private static final Serializer _stringSerializer = new UTF8StringBytes();
-    private static final Serializer _destSerializerV1 = new DestEntrySerializer();
-    private static final Serializer _destSerializerV4 = new DestEntrySerializerV4();
+    private static final Serializer<Properties> _infoSerializer = new PropertiesSerializer();
+    private static final Serializer<String> _stringSerializer = new UTF8StringBytes();
+    private static final Serializer<DestEntry> _destSerializerV1 = new DestEntrySerializer();
+    private static final Serializer<DestEntry> _destSerializerV4 = new DestEntrySerializerV4();
     // upgrade(), initExisting(), and initNew() will change this to _destSerializerV4
-    private volatile Serializer _destSerializer = _destSerializerV1;
-    private static final Serializer _hashIndexSerializer = new IntBytes();
+    private volatile Serializer<DestEntry> _destSerializer = _destSerializerV1;
+    private static final Serializer<Integer> _hashIndexSerializer = new IntBytes();
 
     private static final String HOSTS_DB = "hostsdb.blockfile";
     private static final String FALLBACK_LIST = "hosts.txt";
@@ -1402,14 +1402,13 @@ public class BlockfileNamingService extends DummyNamingService {
      *  but if we threw a RuntimeException we would prevent access to entries later in
      *  the SkipSpan.
      */
-    private static class DestEntrySerializer implements Serializer {
+    private static class DestEntrySerializer implements Serializer<DestEntry> {
 
         /**
          *  A format error on the properties is non-fatal (only the properties are lost)
          *  A format error on the destination is fatal
          */
-        public byte[] getBytes(Object o) {
-            DestEntry de = (DestEntry) o;
+        public byte[] getBytes(DestEntry de) {
             ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
             try {
                 try {
@@ -1429,7 +1428,7 @@ public class BlockfileNamingService extends DummyNamingService {
         }
 
         /** returns null on error */
-        public Object construct(byte[] b) {
+        public DestEntry construct(byte[] b) {
             DestEntry rv = new DestEntry();
             ByteArrayInputStream bais = new ByteArrayInputStream(b);
             try {
@@ -1452,10 +1451,9 @@ public class BlockfileNamingService extends DummyNamingService {
      *  For multiple destinations per hostname
      *  @since 0.9.26
      */
-    private static class DestEntrySerializerV4 implements Serializer {
+    private static class DestEntrySerializerV4 implements Serializer<DestEntry> {
 
-        public byte[] getBytes(Object o) {
-            DestEntry de = (DestEntry) o;
+        public byte[] getBytes(DestEntry de) {
             ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
             int sz = de.destList != null ? de.destList.size() : 1;
             try {
@@ -1487,7 +1485,7 @@ public class BlockfileNamingService extends DummyNamingService {
         }
 
         /** returns null on error */
-        public Object construct(byte[] b) {
+        public DestEntry construct(byte[] b) {
             DestEntry rv = new DestEntry();
             ByteArrayInputStream bais = new ByteArrayInputStream(b);
             try {
diff --git a/core/java/src/net/metanotion/io/Serializer.java b/core/java/src/net/metanotion/io/Serializer.java
index 40cab225524ed172b9d3ca55b00fb1aa30cbf011..44e3cf74a42a47c12fee765b1b01dd8aa9f7a607 100644
--- a/core/java/src/net/metanotion/io/Serializer.java
+++ b/core/java/src/net/metanotion/io/Serializer.java
@@ -28,7 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 package net.metanotion.io;
 
-public interface Serializer {
-	public byte[] getBytes(Object o);
-	public Object construct(byte[] b);
+public interface Serializer<T> {
+	public byte[] getBytes(T o);
+	public T construct(byte[] b);
 }
diff --git a/core/java/src/net/metanotion/io/block/BlockFile.java b/core/java/src/net/metanotion/io/block/BlockFile.java
index 5e1b4339953d953ca780241b33f6682b5e77e65c..b220a87f059c8276e8bf6f71d22845a655af4175 100644
--- a/core/java/src/net/metanotion/io/block/BlockFile.java
+++ b/core/java/src/net/metanotion/io/block/BlockFile.java
@@ -96,7 +96,7 @@ public class BlockFile implements Closeable {
 	/** I2P was the file locked when we opened it? */
 	private final boolean _wasMounted;
 
-	private final BSkipList metaIndex;
+	private final BSkipList<String, Integer> metaIndex;
 	private boolean _isClosed;
 	/** cached list of free pages, only valid if freListStart > 0 */
 	private FreeListBlock flb;
@@ -322,7 +322,7 @@ public class BlockFile implements Closeable {
 		if (rai.canWrite())
 			mount();
 
-		metaIndex = new BSkipList(spanSize, this, METAINDEX_PAGE, new StringBytes(), new IntBytes());
+		metaIndex = new BSkipList<String, Integer>(spanSize, this, METAINDEX_PAGE, new StringBytes(), new IntBytes());
 	}
 
 	/**
@@ -442,15 +442,15 @@ public class BlockFile implements Closeable {
 	 *
 	 *  @return null if not found
 	 */
-	public BSkipList getIndex(String name, Serializer key, Serializer val) throws IOException {
+	public <K extends Comparable<? super K>, V> BSkipList<K, V> getIndex(String name, Serializer<K> key, Serializer<V> val) throws IOException {
 		// added I2P
-		BSkipList bsl = openIndices.get(name);
+		BSkipList<K, V> bsl = (BSkipList<K, V>) openIndices.get(name);
 		if (bsl != null)
 			return bsl;
 
-		Integer page = (Integer) metaIndex.get(name);
+		Integer page = metaIndex.get(name);
 		if (page == null) { return null; }
-		bsl = new BSkipList(spanSize, this, page.intValue(), key, val, true);
+		bsl = new BSkipList<K, V>(spanSize, this, page.intValue(), key, val, true);
 		if (file.canWrite()) {
 			log.info("Checking skiplist " + name + " in blockfile " + file);
 			if (bsl.bslck(true, false))
@@ -468,12 +468,12 @@ public class BlockFile implements Closeable {
 	 *
 	 *  @throws IOException if already exists or other errors
 	 */
-	public BSkipList makeIndex(String name, Serializer key, Serializer val) throws IOException {
+	public <K extends Comparable<? super K>, V> BSkipList<K, V> makeIndex(String name, Serializer<K> key, Serializer<V> val) throws IOException {
 		if(metaIndex.get(name) != null) { throw new IOException("Index already exists"); }
 		int page = allocPage();
 		metaIndex.put(name, Integer.valueOf(page));
 		BSkipList.init(this, page, spanSize);
-		BSkipList bsl = new BSkipList(spanSize, this, page, key, val, true);
+		BSkipList<K, V> bsl = new BSkipList<K, V>(spanSize, this, page, key, val, true);
 		openIndices.put(name, bsl);
 		return bsl;
 	}
@@ -516,24 +516,24 @@ public class BlockFile implements Closeable {
 	 *  @throws IOException if it is open or on errors
 	 *  @since 0.9.26
 	 */
-	public void reformatIndex(String name, Serializer oldKey, Serializer oldVal,
-	                          Serializer newKey, Serializer newVal) throws IOException {
+	public <K extends Comparable<? super K>, V> void reformatIndex(String name, Serializer<K> oldKey, Serializer<V> oldVal,
+	                          Serializer<K> newKey, Serializer<V> newVal) throws IOException {
 		if (openIndices.containsKey(name))
 			throw new IOException("Cannot reformat open skiplist " + name);
-		BSkipList old = getIndex(name, oldKey, oldVal);
+		BSkipList<K, V> old = getIndex(name, oldKey, oldVal);
 		if (old == null)
 			return;
 		long start = System.currentTimeMillis();
 		String tmpName = "---tmp---" + name + "---tmp---";
-		BSkipList tmp = makeIndex(tmpName, newKey, newVal);
+		BSkipList<K, V> tmp = makeIndex(tmpName, newKey, newVal);
 
 		// It could be much more efficient to do this at the
 		// SkipSpan layer but that's way too hard.
 		final int loop = 32;
-		List<Comparable> keys = new ArrayList<Comparable>(loop);
-		List<Object> vals = new ArrayList<Object>(loop);
+		List<K> keys = new ArrayList<K>(loop);
+		List<V> vals = new ArrayList<V>(loop);
 		while (true) {
-			SkipIterator iter = old.iterator();
+			SkipIterator<K, V> iter = old.iterator();
 			for (int i = 0; iter.hasNext() && i < loop; i++) {
 				keys.add(iter.nextKey());
 				vals.add(iter.next());
@@ -555,7 +555,7 @@ public class BlockFile implements Closeable {
 		delIndex(name);
 		closeIndex(name);
 		closeIndex(tmpName);
-		Integer page = (Integer) metaIndex.get(tmpName);
+		Integer page = metaIndex.get(tmpName);
 		metaIndex.put(name, page);
 		metaIndex.remove(tmpName);
 		if (log.shouldWarn())
@@ -623,9 +623,15 @@ public class BlockFile implements Closeable {
 			try {
 				// This uses IdentityBytes, so the value class won't be right, but at least
 				// it won't fail the out-of-order check
-				Serializer keyser = slname.equals("%%__REVERSE__%%") ? new IntBytes() : new UTF8StringBytes();
-				BSkipList bsl = getIndex(slname, keyser, new IdentityBytes());
-				if (bsl == null) {
+				boolean fail;
+				if (slname.equals("%%__REVERSE__%%")) {
+					Serializer<Integer> keyser = new IntBytes();
+					fail = getIndex(slname, keyser, new IdentityBytes()) == null;
+				} else {
+					Serializer<String> keyser = new UTF8StringBytes();
+					fail = getIndex(slname, keyser, new IdentityBytes()) == null;
+				}
+				if (fail) {
 					log.error("Can't find list? " + slname);
 					continue;
 				}
diff --git a/core/java/src/net/metanotion/io/block/index/BSkipLevels.java b/core/java/src/net/metanotion/io/block/index/BSkipLevels.java
index dcf5f38dd0ba7019c79a7d054c33256f6cffc90e..5785c2ddadd2e66ee2feca047e1c5712a2bf765d 100644
--- a/core/java/src/net/metanotion/io/block/index/BSkipLevels.java
+++ b/core/java/src/net/metanotion/io/block/index/BSkipLevels.java
@@ -55,13 +55,13 @@ import net.i2p.util.Log;
  *
  * Always fits on one page.
  */
-public class BSkipLevels extends SkipLevels {
+public class BSkipLevels<K extends Comparable<? super K>, V> extends SkipLevels<K, V> {
 	private static final long MAGIC = 0x42534c6576656c73l;  // "BSLevels"
 	static final int HEADER_LEN = 16;
 	public final int levelPage;
 	public final int spanPage;
 	public final BlockFile bf;
-	private final BSkipList bsl;
+	private final BSkipList<K, V> bsl;
 	private boolean isKilled;
 	// the level pages, passed from the constructor to initializeLevels(),
 	// NOT kept up to date
@@ -73,7 +73,7 @@ public class BSkipLevels extends SkipLevels {
 	 *  after the constructor, unless it's a new empty
 	 *  level and init() was previously called.
 	 */
-	public BSkipLevels(BlockFile bf, int levelPage, BSkipList bsl) throws IOException {
+	public BSkipLevels(BlockFile bf, int levelPage, BSkipList<K, V> bsl) throws IOException {
 		this.levelPage = levelPage;
 		this.bf = bf;
 		this.bsl = bsl;
@@ -97,7 +97,7 @@ public class BSkipLevels extends SkipLevels {
 			throw new IOException("No span found in cache???");
 		}
 
-		this.levels = new BSkipLevels[maxLen];
+		this.levels = (BSkipLevels<K, V>[]) new BSkipLevels[maxLen];
 		if (bf.log.shouldLog(Log.DEBUG))
 			bf.log.debug("Reading New BSkipLevels with " + nonNull + " / " + maxLen + " valid levels page " + levelPage +
 				     " in skiplist " + bsl);
@@ -118,14 +118,14 @@ public class BSkipLevels extends SkipLevels {
 	 *  @since 0.9.20
 	 */
 	public void initializeLevels() {
-		List<BSkipLevels> toInit = new ArrayList<BSkipLevels>(32);
-		List<BSkipLevels> nextInit = new ArrayList<BSkipLevels>(32);
+		List<BSkipLevels<K, V>> toInit = new ArrayList<BSkipLevels<K, V>>(32);
+		List<BSkipLevels<K, V>> nextInit = new ArrayList<BSkipLevels<K, V>>(32);
 		initializeLevels(toInit);
 		while (!toInit.isEmpty()) {
-			for (BSkipLevels bsl : toInit) {
+			for (BSkipLevels<K, V> bsl : toInit) {
 				bsl.initializeLevels(nextInit);
 			}
-			List<BSkipLevels> tmp = toInit;
+			List<BSkipLevels<K, V>> tmp = toInit;
 			toInit = nextInit;
 			nextInit = tmp;
 			nextInit.clear();
@@ -139,7 +139,7 @@ public class BSkipLevels extends SkipLevels {
 	 *  @param nextInit out parameter, next levels to initialize
 	 *  @since 0.9.20
 	 */
-	private void initializeLevels(List<BSkipLevels> nextInit) {
+	private void initializeLevels(List<BSkipLevels<K, V>> nextInit) {
 		boolean fail = false;
 		for(int i = 0; i < lps.length; i++) {
 			int lp = lps[i];
@@ -147,7 +147,7 @@ public class BSkipLevels extends SkipLevels {
 				levels[i] = bsl.levelHash.get(Integer.valueOf(lp));
 				if(levels[i] == null) {
 					try {
-						BSkipLevels lev = new BSkipLevels(bf, lp, bsl);
+						BSkipLevels<K, V> lev = new BSkipLevels<K, V>(bf, lp, bsl);
 						levels[i] = lev;
 						nextInit.add(lev);
 					} catch (IOException ioe) {
@@ -158,8 +158,8 @@ public class BSkipLevels extends SkipLevels {
 						continue;
 					}
 				}
-				Comparable ourKey = key();
-				Comparable nextKey = levels[i].key();
+				K ourKey = key();
+				K nextKey = levels[i].key();
 				if (ourKey != null && nextKey != null &&
 				    ourKey.compareTo(nextKey) >= 0) {
 					bf.log.warn("Corrupt database, level out of order " + this +
@@ -215,9 +215,9 @@ public class BSkipLevels extends SkipLevels {
 					break;
 			}
 			bf.file.writeShort(i);
-			bf.file.writeInt(((BSkipSpan) bottom).page);
+			bf.file.writeInt(((BSkipSpan<K, V>) bottom).page);
 			for(int j = 0; j < i; j++) {
-				bf.file.writeInt(((BSkipLevels) levels[j]).levelPage);
+				bf.file.writeInt(((BSkipLevels<K, V>) levels[j]).levelPage);
 			}
 		} catch (IOException ioe) { throw new RuntimeException("Error writing to database", ioe); }
 	}
@@ -236,15 +236,15 @@ public class BSkipLevels extends SkipLevels {
 	}
 
 	@Override
-	public SkipLevels newInstance(int levels, SkipSpan ss, SkipList sl) {
+	public SkipLevels<K, V> newInstance(int levels, SkipSpan<K, V> ss, SkipList<K, V> sl) {
 		try {
-			BSkipSpan bss = (BSkipSpan) ss;
-			BSkipList bsl = (BSkipList) sl;
+			BSkipSpan<K, V> bss = (BSkipSpan<K, V>) ss;
+			BSkipList<K, V> bsl = (BSkipList<K, V>) sl;
 			int page = bf.allocPage();
 			BSkipLevels.init(bf, page, bss.page, levels);
 			if (bf.log.shouldLog(Log.DEBUG))
 				bf.log.debug("New BSkipLevels height " + levels + " page " + page);
-			return new BSkipLevels(bf, page, bsl);
+			return new BSkipLevels<K, V>(bf, page, bsl);
 			// do not need to call initLevels() here
 		} catch (IOException ioe) { throw new RuntimeException("Error creating database page", ioe); }
 	}
@@ -273,7 +273,7 @@ public class BSkipLevels extends SkipLevels {
 	 *  @since 0.8.8
 	 */
 	private boolean blvlfix() {
-		TreeSet<SkipLevels> lvls = new TreeSet<SkipLevels>(new LevelComparator());
+		TreeSet<SkipLevels<K, V>> lvls = new TreeSet<SkipLevels<K, V>>(new LevelComparator<K, V>());
 		if (bf.log.shouldLog(Log.DEBUG))
 			bf.log.debug("Starting level search");
 		getAllLevels(this, lvls);
@@ -285,15 +285,15 @@ public class BSkipLevels extends SkipLevels {
 		}
 		// traverse the levels, back-to-front
 		boolean rv = false;
-		SkipLevels after = null;
-		for (SkipLevels lv : lvls) {
+		SkipLevels<K, V> after = null;
+		for (SkipLevels<K, V> lv : lvls) {
 			boolean modified = false;
 			if (bf.log.shouldLog(Log.DEBUG))
 				bf.log.debug("Checking " + lv.print());
 			if (after != null) {
 				int min = Math.min(after.levels.length, lv.levels.length);
 				for (int i = 0; i < min; i++) {
-					SkipLevels cur = lv.levels[i];
+					SkipLevels<K, V> cur = lv.levels[i];
 					if (cur != after) {
 						if (cur != null)
 							bf.log.warn("Level " + i + " was wrong, fixing for " + lv.print());
@@ -331,12 +331,12 @@ public class BSkipLevels extends SkipLevels {
 	 *  @param lvlSet out parameter, the result
 	 *  @since 0.8.8
 	 */
-	private void getAllLevels(SkipLevels l, Set<SkipLevels> lvlSet) {
+	private void getAllLevels(SkipLevels<K, V> l, Set<SkipLevels<K, V>> lvlSet) {
 		if (bf.log.shouldLog(Log.DEBUG))
 			bf.log.debug("GAL " + l.print());
 		// Do level 0 without recursion, on the assumption everything is findable
 		// from the root
-		SkipLevels cur = l;
+		SkipLevels<K, V> cur = l;
 		while (cur != null && lvlSet.add(cur)) {
 			if (bf.log.shouldLog(Log.DEBUG))
 				bf.log.debug("Adding " + cur.print());
@@ -347,7 +347,7 @@ public class BSkipLevels extends SkipLevels {
 		// If there were no nulls at level 0 in the middle,
 		// i.e. there are no problems, this won't find anything
 		for (int i = 1; i < l.levels.length; i++) {
-			SkipLevels lv = l.levels[i];
+			SkipLevels<K, V> lv = l.levels[i];
 			if (lv != null && !lvlSet.contains(lv))
 				getAllLevels(lv, lvlSet);
 		}
@@ -358,10 +358,10 @@ public class BSkipLevels extends SkipLevels {
          *  Sorts in REVERSE order.
 	 *  @since 0.8.8
 	 */
-	private static class LevelComparator implements Comparator<SkipLevels>, Serializable {
-		public int compare(SkipLevels l, SkipLevels r) {
-			Comparable lk = l.key();
-			Comparable rk = r.key();
+	private static class LevelComparator<K extends Comparable<? super K>, V> implements Comparator<SkipLevels<K, V>>, Serializable {
+		public int compare(SkipLevels<K, V> l, SkipLevels<K, V> r) {
+			K lk = l.key();
+			K rk = r.key();
 			if (lk == null && rk == null)
 				return 0;
 			if (lk == null)
@@ -378,13 +378,13 @@ public class BSkipLevels extends SkipLevels {
 	 *  This needs work.
 	 */
 	@Override
-	public boolean blvlck(boolean fix, int width, SkipLevels[] prevLevels) {
+	public boolean blvlck(boolean fix, int width, SkipLevels<K, V>[] prevLevels) {
 		bf.log.warn("    Skip level at width " + width);
 		bf.log.warn("        levels " + this.levels.length);
 		bf.log.warn("        first key " + this.key());
 		bf.log.warn("        spanPage " + this.spanPage);
 		bf.log.warn("        levelPage " + this.levelPage);
-		SkipLevels higher = null;
+		SkipLevels<K, V> higher = null;
 		for (int i = levels.length - 1; i >= 0; i--) {
 			if (levels[i] != null) {
 				bf.log.info("                level " + i + " -> " + levels[i].key() + " ");
@@ -418,7 +418,7 @@ public class BSkipLevels extends SkipLevels {
 				}
 			}
 		} else {
-			prevLevels = new SkipLevels[levels.length];
+			prevLevels = (SkipLevels<K, V>[]) new SkipLevels[levels.length];
 			System.arraycopy(levels, 0, prevLevels, 0, levels.length);
 		}
 		if (levels[0] != null)
diff --git a/core/java/src/net/metanotion/io/block/index/BSkipList.java b/core/java/src/net/metanotion/io/block/index/BSkipList.java
index 71bc805353be8ec8874f566fdf7393bca36ee67a..12c0a3dc6c1933b6e76c45347c5b799f64791aa0 100644
--- a/core/java/src/net/metanotion/io/block/index/BSkipList.java
+++ b/core/java/src/net/metanotion/io/block/index/BSkipList.java
@@ -51,7 +51,7 @@ import net.i2p.util.Log;
  *
  * Always fits on one page.
  */
-public class BSkipList extends SkipList implements Closeable {
+public class BSkipList<K extends Comparable<? super K>, V> extends SkipList<K, V> implements Closeable {
 	private static final long MAGIC = 0x536b69704c697374l;  // "SkipList"
 	public int firstSpanPage = 0;
 	public int firstLevelPage = 0;
@@ -59,16 +59,16 @@ public class BSkipList extends SkipList implements Closeable {
 	public final BlockFile bf;
 	private boolean isClosed;
 
-	final HashMap<Integer, BSkipSpan> spanHash = new HashMap<Integer, BSkipSpan>();
-	final HashMap<Integer, SkipLevels> levelHash = new HashMap<Integer, SkipLevels>();
+	final HashMap<Integer, BSkipSpan<K, V>> spanHash = new HashMap<Integer, BSkipSpan<K, V>>();
+	final HashMap<Integer, SkipLevels<K, V>> levelHash = new HashMap<Integer, SkipLevels<K, V>>();
 
 	private final boolean fileOnly;
 
-	public BSkipList(int spanSize, BlockFile bf, int skipPage, Serializer key, Serializer val) throws IOException {
+	public BSkipList(int spanSize, BlockFile bf, int skipPage, Serializer<K> key, Serializer<V> val) throws IOException {
 		this(spanSize, bf, skipPage, key, val, false);
 	}
 
-	public BSkipList(int spanSize, BlockFile bf, int skipPage, Serializer key, Serializer val, boolean fileOnly) throws IOException {
+	public BSkipList(int spanSize, BlockFile bf, int skipPage, Serializer<K> key, Serializer<V> val, boolean fileOnly) throws IOException {
 		if(spanSize < 1) { throw new RuntimeException("Span size too small"); }
 
 		this.skipPage = skipPage;
@@ -89,10 +89,10 @@ public class BSkipList extends SkipList implements Closeable {
 
 		this.fileOnly = fileOnly;
 		if (fileOnly)
-			first = new IBSkipSpan(bf, this, firstSpanPage, key, val);
+			first = new IBSkipSpan<K, V>(bf, this, firstSpanPage, key, val);
 		else
-			first = new BSkipSpan(bf, this, firstSpanPage, key, val);
-		BSkipLevels bstack = new BSkipLevels(bf, firstLevelPage, this);
+			first = new BSkipSpan<K, V>(bf, this, firstSpanPage, key, val);
+		BSkipLevels<K, V> bstack = new BSkipLevels<K, V>(bf, firstLevelPage, this);
 		bstack.initializeLevels();
 		stack = bstack;
 		int total = 0;
@@ -199,33 +199,33 @@ public class BSkipList extends SkipList implements Closeable {
 	}
 
 	@Override
-	public SkipIterator iterator() {
+	public SkipIterator<K, V> iterator() {
 		if (!this.fileOnly)
 			return super.iterator();
-		return new IBSkipIterator(first, 0);
+		return new IBSkipIterator<K, V>(first, 0);
 	}
 
 	@Override
-	public SkipIterator min() {
+	public SkipIterator<K, V> min() {
 		return iterator();
 	}
 
 	@Override
-	public SkipIterator max() {
+	public SkipIterator<K, V> max() {
 		if (!this.fileOnly)
 			return super.max();
-		SkipSpan ss = stack.getEnd();
-		return new IBSkipIterator(ss, ss.nKeys - 1);
+		SkipSpan<K, V> ss = stack.getEnd();
+		return new IBSkipIterator<K, V>(ss, ss.nKeys - 1);
 	}
 
 	@Override
-	public SkipIterator find(Comparable key) {
+	public SkipIterator<K, V> find(K key) {
 		if (!this.fileOnly)
 			return super.find(key);
 		int[] search = new int[1];
-		SkipSpan ss = stack.getSpan(stack.levels.length - 1, key, search);
+		SkipSpan<K, V> ss = stack.getSpan(stack.levels.length - 1, key, search);
 		if(search[0] < 0) { search[0] = -1 * (search[0] + 1); }
-		return new IBSkipIterator(ss, search[0]);
+		return new IBSkipIterator<K, V>(ss, search[0]);
 	}
 
 	/**
diff --git a/core/java/src/net/metanotion/io/block/index/BSkipSpan.java b/core/java/src/net/metanotion/io/block/index/BSkipSpan.java
index 551bdca8864a39479a97037ad8ad6fdaa6470a3a..6b975e5d33e2109ebab58093027151d89b3de394 100644
--- a/core/java/src/net/metanotion/io/block/index/BSkipSpan.java
+++ b/core/java/src/net/metanotion/io/block/index/BSkipSpan.java
@@ -59,19 +59,19 @@ import net.i2p.util.Log;
  *     next overflow page (unsigned int)
  *</pre>
  */
-public class BSkipSpan extends SkipSpan {
+public class BSkipSpan<K extends Comparable<? super K>, V> extends SkipSpan<K, V> {
 	protected static final int MAGIC = 0x5370616e;  // "Span"
 	protected static final int HEADER_LEN = 20;
 	public static final int CONT_HEADER_LEN = 8;
 	protected final BlockFile bf;
-	private final BSkipList bsl;
+	private final BSkipList<K, V> bsl;
 	protected int page;
 	protected int overflowPage;
 
 	protected int prevPage;
 	protected int nextPage = 0;
-	protected Serializer keySer;
-	protected Serializer valSer;
+	protected Serializer<K> keySer;
+	protected Serializer<V> valSer;
 
 	// I2P
 	protected int spanSize;
@@ -88,11 +88,11 @@ public class BSkipSpan extends SkipSpan {
 	}
 
 	@Override
-	public SkipSpan newInstance(SkipList sl) {
+	public SkipSpan<K, V> newInstance(SkipList<K, V> sl) {
 		try {
 			int newPage = bf.allocPage();
 			init(bf, newPage, bf.spanSize);
-			return new BSkipSpan(bf, (BSkipList) sl, newPage, keySer, valSer);
+			return new BSkipSpan<K, V>(bf, (BSkipList<K, V>) sl, newPage, keySer, valSer);
 		} catch (IOException ioe) { throw new RuntimeException("Error creating database page", ioe); }
 	}
 
@@ -237,7 +237,8 @@ public class BSkipSpan extends SkipSpan {
 		//bsl.flush();
 	}
 
-	private static void load(BSkipSpan bss, BlockFile bf, BSkipList bsl, int spanPage, Serializer key, Serializer val) throws IOException {
+	private static <X extends Comparable<? super X>, Y> void load(BSkipSpan<X, Y> bss, BlockFile bf, BSkipList<X, Y> bsl,
+	                                                   int spanPage, Serializer<X> key, Serializer<Y> val) throws IOException {
 		loadInit(bss, bf, bsl, spanPage, key, val);
 		bss.loadData();
 	}
@@ -246,7 +247,8 @@ public class BSkipSpan extends SkipSpan {
 	 * I2P - first half of load()
 	 * Only read the span headers
 	 */
-	protected static void loadInit(BSkipSpan bss, BlockFile bf, BSkipList bsl, int spanPage, Serializer key, Serializer val) throws IOException {
+	protected static <X extends Comparable<? super X>, Y> void loadInit(BSkipSpan<X, Y> bss, BlockFile bf, BSkipList<X, Y> bsl,
+	                                                         int spanPage, Serializer<X> key, Serializer<Y> val) throws IOException {
 		if (bss.isKilled)
 			throw new IOException("Already killed!! " + bss);
 		bss.page = spanPage;
@@ -288,8 +290,8 @@ public class BSkipSpan extends SkipSpan {
 	protected void loadData(boolean flushOnError) throws IOException {
 		if (isKilled)
 			throw new IOException("Already killed!! " + this);
-		this.keys = new Comparable[this.spanSize];
-		this.vals = new Object[this.spanSize];
+		this.keys = (K[]) new Comparable[this.spanSize];
+		this.vals = (V[]) new Object[this.spanSize];
 
 		int ksz, vsz;
 		int curPage = this.page;
@@ -327,7 +329,7 @@ public class BSkipSpan extends SkipSpan {
 				break;
 			}
 //			System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz);
-			this.keys[i] = (Comparable) this.keySer.construct(k);
+			this.keys[i] = this.keySer.construct(k);
 			this.vals[i] = this.valSer.construct(v);
 			// Drop bad entry without throwing exception
 			if (this.keys[i] == null || this.vals[i] == null) {
@@ -377,31 +379,31 @@ public class BSkipSpan extends SkipSpan {
 		}
 	}
 
-	protected BSkipSpan(BlockFile bf, BSkipList bsl) {
+	protected BSkipSpan(BlockFile bf, BSkipList<K, V> bsl) {
 		this.bf = bf;
 		this.bsl = bsl;
 	}
 
-	public BSkipSpan(BlockFile bf, BSkipList bsl, int spanPage, Serializer key, Serializer val) throws IOException {
+	public BSkipSpan(BlockFile bf, BSkipList<K, V> bsl, int spanPage, Serializer<K> key, Serializer<V> val) throws IOException {
 		this.bf = bf;
 		this.bsl = bsl;
 		BSkipSpan.load(this, bf, bsl, spanPage, key, val);
 		this.next = null;
 		this.prev = null;
 
-		BSkipSpan bss = this;
+		BSkipSpan<K, V> bss = this;
 		// findbugs ok (set in load() above)
 		int np = nextPage;
 		while(np != 0) {
-			BSkipSpan temp = bsl.spanHash.get(Integer.valueOf(np));
+			BSkipSpan<K, V> temp = bsl.spanHash.get(Integer.valueOf(np));
 			if(temp != null) {
 				bss.next = temp;
 				break;
 			}
-			bss.next = new BSkipSpan(bf, bsl);
+			bss.next = new BSkipSpan<K, V>(bf, bsl);
 			bss.next.next = null;
 			bss.next.prev = bss;
-			bss = (BSkipSpan) bss.next;
+			bss = (BSkipSpan<K, V>) bss.next;
 			
 			BSkipSpan.load(bss, bf, bsl, np, key, val);
 			np = bss.nextPage;
@@ -411,15 +413,15 @@ public class BSkipSpan extends SkipSpan {
 		bss = this;
 		np = prevPage;
 		while(np != 0) {
-			BSkipSpan temp = bsl.spanHash.get(Integer.valueOf(np));
+			BSkipSpan<K, V> temp = bsl.spanHash.get(Integer.valueOf(np));
 			if(temp != null) {
 				bss.prev = temp;
 				break;
 			}
-			bss.prev = new BSkipSpan(bf, bsl);
+			bss.prev = new BSkipSpan<K, V>(bf, bsl);
 			bss.prev.next = bss;
 			bss.prev.prev = null;
-			bss = (BSkipSpan) bss.prev;
+			bss = (BSkipSpan<K, V>) bss.prev;
 			
 			BSkipSpan.load(bss, bf, bsl, np, key, val);
 			np = bss.prevPage;
diff --git a/core/java/src/net/metanotion/io/block/index/IBSkipIterator.java b/core/java/src/net/metanotion/io/block/index/IBSkipIterator.java
index 732862e3b31f8758f89da37ab5f24c7972ad2087..1623b192acbffaab4fc8ff3866f5ca59ab478017 100644
--- a/core/java/src/net/metanotion/io/block/index/IBSkipIterator.java
+++ b/core/java/src/net/metanotion/io/block/index/IBSkipIterator.java
@@ -41,9 +41,9 @@ import net.metanotion.util.skiplist.SkipSpan;
 	If the caller does not iterate all the way through, the last span
 	will remain in memory.
 */
-public class IBSkipIterator extends SkipIterator {
+public class IBSkipIterator<K extends Comparable<? super K>, V> extends SkipIterator<K, V> {
 
-	public IBSkipIterator(SkipSpan ss, int index) {
+	public IBSkipIterator(SkipSpan<K, V> ss, int index) {
 		super(ss, index);
 	}
 
@@ -53,8 +53,8 @@ public class IBSkipIterator extends SkipIterator {
 	 * @throws RuntimeException on IOE
 	 */
 	@Override
-	public Object next() {
-		Object o;
+	public V next() {
+		V o;
 		if(index < ss.nKeys) {
 			if (ss.vals == null) {
 				try {
@@ -90,7 +90,7 @@ public class IBSkipIterator extends SkipIterator {
 	 * @throws RuntimeException on IOE
 	 */
 	@Override
-	public Comparable nextKey() {
+	public K nextKey() {
 		if(index < ss.nKeys) {
 			if (ss.keys == null) {
 				try {
@@ -110,7 +110,7 @@ public class IBSkipIterator extends SkipIterator {
 	 * @throws RuntimeException on IOE
 	 */
 	@Override
-	public Object previous() {
+	public V previous() {
 		if(index > 0) {
 			index--;
 		} else if(ss.prev != null) {
diff --git a/core/java/src/net/metanotion/io/block/index/IBSkipSpan.java b/core/java/src/net/metanotion/io/block/index/IBSkipSpan.java
index d5d33cf9688ee32252021c80d5179f515fc160cb..d7cc26ba4efbbfbf943db0017ed6fa2884946fc8 100644
--- a/core/java/src/net/metanotion/io/block/index/IBSkipSpan.java
+++ b/core/java/src/net/metanotion/io/block/index/IBSkipSpan.java
@@ -54,21 +54,21 @@ import net.i2p.util.Log;
  *
  * @author zzz
  */
-public class IBSkipSpan extends BSkipSpan {
+public class IBSkipSpan<K extends Comparable<? super K>, V> extends BSkipSpan<K, V> {
 
-	private Comparable firstKey;
+	private K firstKey;
 
 	@Override
-	public SkipSpan newInstance(SkipList sl) {
+	public SkipSpan<K, V> newInstance(SkipList<K, V> sl) {
 		if (bf.log.shouldLog(Log.DEBUG))
 			bf.log.debug("Splitting page " + this.page + " containing " + this.nKeys + '/' + this.spanSize);
 		try {
 			int newPage = bf.allocPage();
 			init(bf, newPage, bf.spanSize);
-			SkipSpan rv = new IBSkipSpan(bf, (BSkipList) sl, newPage, keySer, valSer);
+			SkipSpan<K, V> rv = new IBSkipSpan<K, V>(bf, (BSkipList<K, V>) sl, newPage, keySer, valSer);
 			// this is called after a split, so we need the data arrays initialized
-			rv.keys = new Comparable[bf.spanSize];
-			rv.vals = new Object[bf.spanSize];
+			rv.keys = (K[]) new Comparable[bf.spanSize];
+			rv.vals = (V[]) new Object[bf.spanSize];
 			return rv;
 		} catch (IOException ioe) { throw new RuntimeException("Error creating database page", ioe); }
 	}
@@ -125,7 +125,7 @@ public class IBSkipSpan extends BSkipSpan {
 		pageCounter[0] +=4;
 		byte[] k = new byte[ksz];
 		curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage);
-		this.firstKey = (Comparable) this.keySer.construct(k);
+		this.firstKey = this.keySer.construct(k);
 		if (this.firstKey == null) {
 			bf.log.error("Null deserialized first key in page " + curPage);
 			repair(1);
@@ -160,7 +160,7 @@ public class IBSkipSpan extends BSkipSpan {
 	/**
 	 * Linear search through the span in the file for the value.
 	 */
-	private Object getData(Comparable key) throws IOException {
+	private V getData(K key) throws IOException {
 		seekData();
 		int curPage = this.page;
 		int[] curNextPage = new int[1];
@@ -194,7 +194,7 @@ public class IBSkipSpan extends BSkipSpan {
 				break;
 			}
 			//System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz);
-			Comparable ckey = (Comparable) this.keySer.construct(k);
+			K ckey = this.keySer.construct(k);
 			if (ckey == null) {
 				// skip the value and keep going
 				curPage = this.bf.skipMultiPageBytes(vsz, curPage, pageCounter, curNextPage);
@@ -213,7 +213,7 @@ public class IBSkipSpan extends BSkipSpan {
 					lostEntries(i, curPage);
 					break;
 				}
-				Object rv = this.valSer.construct(v);
+				V rv = this.valSer.construct(v);
 				if (rv == null) {
 					bf.log.error("Null deserialized value in entry " + i + " page " + curPage +
 					                    " key=" + ckey);
@@ -252,11 +252,11 @@ public class IBSkipSpan extends BSkipSpan {
 	*****/
 	}
 
-	private IBSkipSpan(BlockFile bf, BSkipList bsl) {
+	private IBSkipSpan(BlockFile bf, BSkipList<K, V> bsl) {
 		super(bf, bsl);
 	}
 
-	public IBSkipSpan(BlockFile bf, BSkipList bsl, int spanPage, Serializer key, Serializer val) throws IOException {
+	public IBSkipSpan(BlockFile bf, BSkipList<K, V> bsl, int spanPage, Serializer<K> key, Serializer<V> val) throws IOException {
 		super(bf, bsl);
 		if (bf.log.shouldLog(Log.DEBUG))
 			bf.log.debug("New ibss page " + spanPage);
@@ -265,24 +265,24 @@ public class IBSkipSpan extends BSkipSpan {
 		this.next = null;
 		this.prev = null;
 
-		IBSkipSpan bss = this;
-		IBSkipSpan temp;
+		IBSkipSpan<K, V> bss = this;
+		IBSkipSpan<K, V> temp;
 		int np = nextPage;
 		while(np != 0) {
-			temp = (IBSkipSpan) bsl.spanHash.get(Integer.valueOf(np));
+			temp = (IBSkipSpan<K, V>) bsl.spanHash.get(Integer.valueOf(np));
 			if(temp != null) {
 				bss.next = temp;
 				break;
 			}
-			bss.next = new IBSkipSpan(bf, bsl);
+			bss.next = new IBSkipSpan<K, V>(bf, bsl);
 			bss.next.next = null;
 			bss.next.prev = bss;
-			Comparable previousFirstKey = bss.firstKey;
-			bss = (IBSkipSpan) bss.next;
+			K previousFirstKey = bss.firstKey;
+			bss = (IBSkipSpan<K, V>) bss.next;
 			
 			BSkipSpan.loadInit(bss, bf, bsl, np, key, val);
 			bss.loadFirstKey();
-			Comparable nextFirstKey = bss.firstKey;
+			K nextFirstKey = bss.firstKey;
 			if (previousFirstKey == null || nextFirstKey == null ||
 			    previousFirstKey.compareTo(nextFirstKey) >= 0) {
 				// TODO remove, but if we are at the bottom of a level
@@ -299,20 +299,20 @@ public class IBSkipSpan extends BSkipSpan {
 		bss = this;
 		np = prevPage;
 		while(np != 0) {
-			temp = (IBSkipSpan) bsl.spanHash.get(Integer.valueOf(np));
+			temp = (IBSkipSpan<K, V>) bsl.spanHash.get(Integer.valueOf(np));
 			if(temp != null) {
 				bss.prev = temp;
 				break;
 			}
-			bss.prev = new IBSkipSpan(bf, bsl);
+			bss.prev = new IBSkipSpan<K, V>(bf, bsl);
 			bss.prev.next = bss;
 			bss.prev.prev = null;
-			Comparable nextFirstKey = bss.firstKey;
-			bss = (IBSkipSpan) bss.prev;
+			K nextFirstKey = bss.firstKey;
+			bss = (IBSkipSpan<K, V>) bss.prev;
 			
 			BSkipSpan.loadInit(bss, bf, bsl, np, key, val);
 			bss.loadFirstKey();
-			Comparable previousFirstKey = bss.firstKey;
+			K previousFirstKey = bss.firstKey;
 			if (previousFirstKey == null || nextFirstKey == null ||
 			    previousFirstKey.compareTo(nextFirstKey) >= 0) {
 				// TODO remove, but if we are at the bottom of a level
@@ -330,7 +330,7 @@ public class IBSkipSpan extends BSkipSpan {
          * Does not call super, we always store first key here
 	 */
 	@Override
-	public Comparable firstKey() {
+	public K firstKey() {
 		return this.firstKey;
 	}
 
@@ -339,13 +339,13 @@ public class IBSkipSpan extends BSkipSpan {
 	 * This is called only via SkipList.find()
 	 */
 	@Override
-	public SkipSpan getSpan(Comparable key, int[] search) {
+	public SkipSpan<K, V> getSpan(K key, int[] search) {
 		try {
 			seekAndLoadData();
 		} catch (IOException ioe) {
 			throw new RuntimeException("Error reading database", ioe);
 		}
-		SkipSpan rv = super.getSpan(key, search);
+		SkipSpan<K, V> rv = super.getSpan(key, search);
 		this.keys = null;
 		this.vals = null;
 		return rv;
@@ -355,7 +355,7 @@ public class IBSkipSpan extends BSkipSpan {
 	 * Linear search if in file, Binary search if in memory
 	 */
 	@Override
-	public Object get(Comparable key) {
+	public V get(K key) {
 		try {
 			if (nKeys == 0) { return null; }
 			if (this.next != null && this.next.firstKey().compareTo(key) <= 0)
@@ -370,13 +370,13 @@ public class IBSkipSpan extends BSkipSpan {
 	 * Load whole span from file, do the operation, flush out, then null out in-memory data again.
 	 */
 	@Override
-	public SkipSpan put(Comparable key, Object val, SkipList sl)	{
+	public SkipSpan<K, V> put(K key, V val, SkipList<K, V> sl)	{
 		try {
 			seekAndLoadData();
 		} catch (IOException ioe) {
 			throw new RuntimeException("Error reading database", ioe);
 		}
-		SkipSpan rv = super.put(key, val, sl);
+		SkipSpan<K, V> rv = super.put(key, val, sl);
 		// flush() nulls out the data
 		return rv;
 	}
@@ -385,7 +385,7 @@ public class IBSkipSpan extends BSkipSpan {
 	 * Load whole span from file, do the operation, flush out, then null out in-memory data again.
 	 */
 	@Override
-	public Object[] remove(Comparable key, SkipList sl) {
+	public Object[] remove(K key, SkipList<K, V> sl) {
 		if (bf.log.shouldLog(Log.DEBUG))
 			bf.log.debug("Remove " + key + " in " + this);
 		if (nKeys <= 0)
diff --git a/core/java/src/net/metanotion/io/data/IdentityBytes.java b/core/java/src/net/metanotion/io/data/IdentityBytes.java
index 6e83c9b285c5f87d40e4d9ec4a37db38437f3fc1..9a6ef9a0edd6fce16fd81620ca78f1dca9c85046 100644
--- a/core/java/src/net/metanotion/io/data/IdentityBytes.java
+++ b/core/java/src/net/metanotion/io/data/IdentityBytes.java
@@ -35,11 +35,11 @@ import net.metanotion.io.Serializer;
  * Will never return null.
  * Added by I2P.
  */
-public class IdentityBytes implements Serializer {
+public class IdentityBytes implements Serializer<byte[]> {
 
 	/** @return byte[] */
-	public byte[] getBytes(Object o) { return (byte[])o; }
+	public byte[] getBytes(byte[] o) { return o; }
 
 	/** @return b */
-	public Object construct(byte[] b) { return b; }
+	public byte[] construct(byte[] b) { return b; }
 }
diff --git a/core/java/src/net/metanotion/io/data/IntBytes.java b/core/java/src/net/metanotion/io/data/IntBytes.java
index 89845040c2a20c17d6f486cd52fe6c673a79b0ff..28abf27145a0f60c58f77e70f655ce60d71e1780 100644
--- a/core/java/src/net/metanotion/io/data/IntBytes.java
+++ b/core/java/src/net/metanotion/io/data/IntBytes.java
@@ -30,10 +30,10 @@ package net.metanotion.io.data;
 
 import net.metanotion.io.Serializer;
 
-public class IntBytes implements Serializer {
-	public byte[] getBytes(Object o) {
+public class IntBytes implements Serializer<Integer> {
+	public byte[] getBytes(Integer o) {
 		byte[] b = new byte[4];
-		int v = ((Integer) o).intValue();
+		int v = o.intValue();
  		b[0] = (byte)(0xff & (v >> 24));
  		b[1] = (byte)(0xff & (v >> 16));
 		b[2] = (byte)(0xff & (v >>  8));
@@ -41,7 +41,7 @@ public class IntBytes implements Serializer {
  		return b;
 	}
 
-	public Object construct(byte[] b) {
+	public Integer construct(byte[] b) {
 		int v = (((b[0] & 0xff) << 24) |
 				 ((b[1] & 0xff) << 16) |
 				 ((b[2] & 0xff) <<  8) |
diff --git a/core/java/src/net/metanotion/io/data/LongBytes.java b/core/java/src/net/metanotion/io/data/LongBytes.java
index fe06e5120450ac8abb34ca7f379ea13437482cd6..cc1ed3c5826aeb3e341c58623bf2066c2cfa5b98 100644
--- a/core/java/src/net/metanotion/io/data/LongBytes.java
+++ b/core/java/src/net/metanotion/io/data/LongBytes.java
@@ -30,10 +30,10 @@ package net.metanotion.io.data;
 
 import net.metanotion.io.Serializer;
 
-public class LongBytes implements Serializer {
-	public byte[] getBytes(Object o) {
+public class LongBytes implements Serializer<Long> {
+	public byte[] getBytes(Long o) {
 		byte[] b = new byte[8];
-		long v = ((Long) o).longValue();
+		long v = o.longValue();
  		b[0] = (byte)(0xff & (v >> 56));
 		b[1] = (byte)(0xff & (v >> 48));
  		b[2] = (byte)(0xff & (v >> 40));
@@ -45,7 +45,7 @@ public class LongBytes implements Serializer {
  		return b;
 	}
 
-	public Object construct(byte[] b) {
+	public Long construct(byte[] b) {
 		long v =(((long)(b[0] & 0xff) << 56) |
 				 ((long)(b[1] & 0xff) << 48) |
 				 ((long)(b[2] & 0xff) << 40) |
diff --git a/core/java/src/net/metanotion/io/data/NullBytes.java b/core/java/src/net/metanotion/io/data/NullBytes.java
index c3849b0ed588bbf9d31c25efa4f93ce2135a387c..fb51a124112a9770f57577030492e1d253521f57 100644
--- a/core/java/src/net/metanotion/io/data/NullBytes.java
+++ b/core/java/src/net/metanotion/io/data/NullBytes.java
@@ -30,7 +30,7 @@ package net.metanotion.io.data;
 
 import net.metanotion.io.Serializer;
 
-public class NullBytes implements Serializer {
+public class NullBytes implements Serializer<Object> {
 	public byte[] getBytes(Object o) { return null; }
 	public Object construct(byte[] b) { return null; }
 }
diff --git a/core/java/src/net/metanotion/io/data/StringBytes.java b/core/java/src/net/metanotion/io/data/StringBytes.java
index 18740b7b5a942c677de3e96fe29e86f4ed6a0a5a..8237783489d800c7156ad636f43a23d3b559db3e 100644
--- a/core/java/src/net/metanotion/io/data/StringBytes.java
+++ b/core/java/src/net/metanotion/io/data/StringBytes.java
@@ -32,14 +32,14 @@ import java.io.UnsupportedEncodingException;
 
 import net.metanotion.io.Serializer;
 
-public class StringBytes implements Serializer {
-	public byte[] getBytes(Object o) {
+public class StringBytes implements Serializer<String> {
+	public byte[] getBytes(String o) {
 		try {
-			return ((String) o).getBytes("US-ASCII");
+			return o.getBytes("US-ASCII");
 		} catch (UnsupportedEncodingException uee) { throw new Error("Unsupported Encoding"); }
 	}
 
-	public Object construct(byte[] b) {
+	public String construct(byte[] b) {
 		try {
 			return new String(b, "US-ASCII");
 		} catch (UnsupportedEncodingException uee) { throw new Error("Unsupported Encoding"); }
diff --git a/core/java/src/net/metanotion/io/data/UTF8StringBytes.java b/core/java/src/net/metanotion/io/data/UTF8StringBytes.java
index 3924aee941e258555e82eaa9dc3d9c0d5e18e9b0..44876c82032b0d21fdb6fa7bfa48220e737bfc53 100644
--- a/core/java/src/net/metanotion/io/data/UTF8StringBytes.java
+++ b/core/java/src/net/metanotion/io/data/UTF8StringBytes.java
@@ -35,14 +35,14 @@ import net.metanotion.io.Serializer;
 /**
  * Added by I2P
  */
-public class UTF8StringBytes implements Serializer {
-	public byte[] getBytes(Object o) {
+public class UTF8StringBytes implements Serializer<String> {
+	public byte[] getBytes(String o) {
 		try {
-			return ((String) o).getBytes("UTF-8");
+			return o.getBytes("UTF-8");
 		} catch (UnsupportedEncodingException uee) { throw new Error("Unsupported Encoding"); }
 	}
 
-	public Object construct(byte[] b) {
+	public String construct(byte[] b) {
 		try {
 			return new String(b, "UTF-8");
 		} catch (UnsupportedEncodingException uee) { throw new Error("Unsupported Encoding"); }
diff --git a/core/java/src/net/metanotion/util/skiplist/SkipIterator.java b/core/java/src/net/metanotion/util/skiplist/SkipIterator.java
index b0793d638baf9df6ea64742cd196d577fc88b480..9bbeb0bd8415351380af7dc5dd8e1357b31385d2 100644
--- a/core/java/src/net/metanotion/util/skiplist/SkipIterator.java
+++ b/core/java/src/net/metanotion/util/skiplist/SkipIterator.java
@@ -39,12 +39,13 @@ import java.util.NoSuchElementException;
 	To be clear, this is an iterator through the values.
 	To get the key, call nextKey() BEFORE calling next().
 */
-public class SkipIterator implements ListIterator {
-	protected SkipSpan ss;
+public class SkipIterator<K extends Comparable<? super K>, V> implements ListIterator<V> {
+	protected SkipSpan<K, V> ss;
 	protected int index;
 
 	protected SkipIterator() { }
-	public SkipIterator(SkipSpan ss, int index) {
+
+	public SkipIterator(SkipSpan<K, V> ss, int index) {
 		if(ss==null) { throw new NullPointerException(); }
 		this.ss = ss;
 		this.index = index;
@@ -59,8 +60,8 @@ public class SkipIterator implements ListIterator {
 	 * @return the next value, and advances the index
 	 * @throws NoSuchElementException
 	 */
-	public Object next() {
-		Object o;
+	public V next() {
+		V o;
 		if(index < ss.nKeys) {
 			o = ss.vals[index];
 		} else {
@@ -83,7 +84,7 @@ public class SkipIterator implements ListIterator {
 	 * @return the key for which the value will be returned in the subsequent call to next()
 	 * @throws NoSuchElementException
 	 */
-	public Comparable nextKey() {
+	public K nextKey() {
 		if(index < ss.nKeys) { return ss.keys[index]; }
 		throw new NoSuchElementException();
 	}
@@ -98,7 +99,7 @@ public class SkipIterator implements ListIterator {
 	 * @return the previous value, and decrements the index
 	 * @throws NoSuchElementException
 	 */
-	public Object previous() {
+	public V previous() {
 		if(index > 0) {
 			index--;
 		} else if(ss.prev != null) {
@@ -111,9 +112,9 @@ public class SkipIterator implements ListIterator {
 
 
 	// Optional methods
-	public void add(Object o)	{ throw new UnsupportedOperationException(); }
+	public void add(V o)	{ throw new UnsupportedOperationException(); }
 	public void remove()		{ throw new UnsupportedOperationException(); }
-	public void set(Object o)	{ throw new UnsupportedOperationException(); }
+	public void set(V o)	{ throw new UnsupportedOperationException(); }
 	public int nextIndex()		{ throw new UnsupportedOperationException(); }
 	public int previousIndex()	{ throw new UnsupportedOperationException(); }
 
diff --git a/core/java/src/net/metanotion/util/skiplist/SkipLevels.java b/core/java/src/net/metanotion/util/skiplist/SkipLevels.java
index bd9da7ba16b1f821b3da6caeb943a4948d29df12..226660193b86c2797e8ea55d78dd7136b774c134 100644
--- a/core/java/src/net/metanotion/util/skiplist/SkipLevels.java
+++ b/core/java/src/net/metanotion/util/skiplist/SkipLevels.java
@@ -35,7 +35,7 @@ import net.metanotion.io.block.BlockFile;
 import net.i2p.I2PAppContext;
 import net.i2p.util.Log;
 
-public class SkipLevels implements Flushable {
+public class SkipLevels<K extends Comparable<? super K>, V> implements Flushable {
 	/** We can't have more than 2**32 pages */
 	public static final int MAX_SIZE = 32;
 
@@ -45,12 +45,15 @@ public class SkipLevels implements Flushable {
 	 *	The "bottom" level is the direct pointer to a SkipSpan.
 	 */
 	// levels is almost final
-	public SkipLevels[] levels;
+	public SkipLevels<K, V>[] levels;
 	// bottom is final
-	public SkipSpan bottom;
+	public SkipSpan<K, V> bottom;
 	private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(BlockFile.class);
 
-	public SkipLevels newInstance(int levels, SkipSpan ss, SkipList sl) { return new SkipLevels(levels, ss); }
+	public SkipLevels<K, V> newInstance(int levels, SkipSpan<K, V> ss, SkipList<K, V> sl) {
+		return new SkipLevels<K, V>(levels, ss);
+	}
+
 	public void killInstance() { }
 	public void flush() { }
 
@@ -59,10 +62,10 @@ public class SkipLevels implements Flushable {
 	/*
 	 *  @throws IllegalArgumentException if size too big or too small
 	 */
-	public SkipLevels(int size, SkipSpan span) {
+	public SkipLevels(int size, SkipSpan<K, V> span) {
 		if(size < 1 || size > MAX_SIZE)
 			throw new IllegalArgumentException("Invalid Level Skip size");
-		levels = new SkipLevels[size];
+		levels = (SkipLevels<K, V>[]) new SkipLevels[size];
 		bottom = span;
 	}
 
@@ -92,14 +95,14 @@ public class SkipLevels implements Flushable {
 		return buf.toString();
 	}
 
-	public SkipSpan getEnd() {
+	public SkipSpan<K, V> getEnd() {
 		for(int i=(levels.length - 1);i>=0;i--) {
 			if(levels[i] != null) { return levels[i].getEnd(); }
 		}
 		return bottom.getEnd();
 	}
 
-	public SkipSpan getSpan(int start, Comparable key, int[] search) {
+	public SkipSpan<K, V> getSpan(int start, K key, int[] search) {
 		for(int i=Math.min(start, levels.length - 1);i>=0;i--) {
 			if((levels[i] != null) && (levels[i].key().compareTo(key) <= 0)) {
 				return levels[i].getSpan(i,key,search);
@@ -108,9 +111,9 @@ public class SkipLevels implements Flushable {
 		return bottom.getSpan(key, search);
 	}
 
-	public Comparable key() { return bottom.firstKey(); }
+	public K key() { return bottom.firstKey(); }
 
-	public Object get(int start, Comparable key) {
+	public V get(int start, K key) {
 		for(int i=Math.min(start, levels.length - 1);i>=0;i--) {
 			if((levels[i] != null) && (levels[i].key().compareTo(key) <= 0)) {
 				return levels[i].get(i,key);
@@ -126,16 +129,16 @@ public class SkipLevels implements Flushable {
 	 *                and the deleted SkipLevels is taller than this SkipLevels.
 	 *          rv is null if no object was removed.
 	 */
-	public Object[] remove(int start, Comparable key, SkipList sl) {
+	public Object[] remove(int start, K key, SkipList<K, V> sl) {
 		Object[] res = null;
-		SkipLevels slvls = null;
+		SkipLevels<K, V> slvls = null;
 		for(int i = Math.min(start, levels.length - 1); i >= 0; i--) {
 			if(levels[i] != null) {
 				int cmp = levels[i].key().compareTo(key);
 				if((cmp < 0) || ((i==0) && (cmp <= 0)))  {
 					res = levels[i].remove(i, key, sl);
 					if((res != null) && (res[1] != null)) {
-						slvls = (SkipLevels) res[1];
+						slvls = (SkipLevels<K, V>) res[1];
 						if(levels.length >= slvls.levels.length) {
 							res[1] = null;
 						}
@@ -159,7 +162,7 @@ public class SkipLevels implements Flushable {
 				// if the returned SkipSpan was already copied to us
 				boolean isFirst = sl.first == bottom;
 				if (isFirst && levels[0] != null) {
-					SkipSpan ssres = (SkipSpan)res[1];
+					SkipSpan<K, V> ssres = (SkipSpan<K, V>)res[1];
 					if (bottom.firstKey().equals(ssres.firstKey())) {
 						// bottom copied the next span to itself
 						if (_log.shouldLog(Log.INFO)) {
@@ -171,7 +174,7 @@ public class SkipLevels implements Flushable {
 							_log.info("FIXUP TIME");
 						}
 						
-						SkipLevels replace = levels[0];
+						SkipLevels<K, V> replace = levels[0];
 						for (int i = 0; i < levels.length; i++) {
 							if (levels[i] == null)
 								break;
@@ -213,12 +216,12 @@ public class SkipLevels implements Flushable {
 	 *          and the new level is taller than our level;
 	 *          else null if it went in an existing level or the new level is our height or less.
 	 */
-	public SkipLevels put(int start, Comparable key, Object val, SkipList sl) {
+	public SkipLevels<K, V> put(int start, K key, V val, SkipList<K, V> sl) {
 		boolean modified = false;
 		for(int i = Math.min(start, levels.length - 1); i >= 0; i--) {
 			// is key equal to or after the start of the level?
 			if((levels[i] != null) && (levels[i].key().compareTo(key) <= 0)) {
-				SkipLevels slvls = levels[i].put(i, key, val, sl);
+				SkipLevels<K, V> slvls = levels[i].put(i, key, val, sl);
 				if(slvls != null) {
 					for (int j = i + 1; j < Math.min(slvls.levels.length, levels.length); j++) {
 						// he points to where we used to point
@@ -243,11 +246,11 @@ public class SkipLevels implements Flushable {
 				return null;
 			}
 		}
-		SkipSpan ss = bottom.put(key,val,sl);
+		SkipSpan<K, V> ss = bottom.put(key,val,sl);
 		if(ss!=null) {
 			int height = sl.generateColHeight();
 			if(height != 0) {
-				SkipLevels slvls = this.newInstance(height, ss, sl);
+				SkipLevels<K, V> slvls = this.newInstance(height, ss, sl);
 				for(int i=0;i<(Math.min(height,levels.length));i++) {
 					// he points to where we used to point
 					// and we now point to him
@@ -267,6 +270,6 @@ public class SkipLevels implements Flushable {
 	}
 
 	public boolean blvlck(boolean fix) { return false; }
-	public boolean blvlck(boolean fix, int width, SkipLevels[] prevLevels) { return false; }
+	public boolean blvlck(boolean fix, int width, SkipLevels<K, V>[] prevLevels) { return false; }
 }
 
diff --git a/core/java/src/net/metanotion/util/skiplist/SkipList.java b/core/java/src/net/metanotion/util/skiplist/SkipList.java
index 6619485f223117112acf8e982cd409bff827924f..32c3e7231387318b05658dd21647a13e3e94edbe 100644
--- a/core/java/src/net/metanotion/util/skiplist/SkipList.java
+++ b/core/java/src/net/metanotion/util/skiplist/SkipList.java
@@ -35,13 +35,13 @@ import net.i2p.util.RandomSource;
 
 //import net.metanotion.io.block.BlockFile;
 
-public class SkipList implements Flushable {
+public class SkipList<K extends Comparable<? super K>, V> implements Flushable {
 	/** the probability of each next higher level */
 	protected static final int P = 2;
 	private static final int MIN_SLOTS = 4;
 	// these two are really final
-	protected SkipSpan first;
-	protected SkipLevels stack;
+	protected SkipSpan<K, V> first;
+	protected SkipLevels<K, V> stack;
 	// I2P mod
 	public static final Random rng = RandomSource.getInstance();
 
@@ -57,8 +57,8 @@ public class SkipList implements Flushable {
 	public SkipList(int span) {
 		if(span < 1 || span > SkipSpan.MAX_SIZE)
 			throw new IllegalArgumentException("Invalid span size");
-		first = new SkipSpan(span);
-		stack = new SkipLevels(1, first);
+		first = new SkipSpan<K, V>(span);
+		stack = new SkipLevels<K, V>(1, first);
 		//rng = new Random(System.currentTimeMillis());
 	}
 
@@ -95,14 +95,14 @@ public class SkipList implements Flushable {
 		return max;
 	}
 
-	public void put(Comparable key, Object val)	{
+	public void put(K key, V val)	{
 		if(key == null) { throw new NullPointerException(); }
 		if(val == null) { throw new NullPointerException(); }
-		SkipLevels slvls = stack.put(stack.levels.length - 1, key, val, this);
+		SkipLevels<K, V> slvls = stack.put(stack.levels.length - 1, key, val, this);
 		if(slvls != null) {
 			// grow our stack
 			//BlockFile.log.info("Top level old hgt " + stack.levels.length +  " new hgt " + slvls.levels.length);
-			SkipLevels[] levels = new SkipLevels[slvls.levels.length];
+			SkipLevels<K, V>[] levels = (SkipLevels<K, V>[]) new SkipLevels[slvls.levels.length];
 			for(int i=0;i < slvls.levels.length; i++) {
 				if(i < stack.levels.length) {
 					levels[i] = stack.levels[i];
@@ -116,12 +116,12 @@ public class SkipList implements Flushable {
 		}
 	}
 
-	public Object remove(Comparable key) {
+	public Object remove(K key) {
 		if(key == null) { throw new NullPointerException(); }
 		Object[] res = stack.remove(stack.levels.length - 1, key, this);
 		if(res != null) {
 			if(res[1] != null) {
-				SkipLevels slvls = (SkipLevels) res[1];
+				SkipLevels<K, V> slvls = (SkipLevels<K, V>) res[1];
 				for(int i=0;i < slvls.levels.length; i++) {
 					if(stack.levels[i] == slvls) {
 						stack.levels[i] = slvls.levels[i];
@@ -154,26 +154,26 @@ public class SkipList implements Flushable {
 		System.out.println(first.print());
 	}
 
-	public Object get(Comparable key) {
+	public V get(K key) {
 		if(key == null) { throw new NullPointerException(); }
 		return stack.get(stack.levels.length - 1, key);
 	}
 
-	public SkipIterator iterator() { return new SkipIterator(first, 0); }
+	public SkipIterator<K, V> iterator() { return new SkipIterator<K, V>(first, 0); }
 
-	public SkipIterator min() { return new SkipIterator(first, 0); }
+	public SkipIterator<K, V> min() { return new SkipIterator<K, V>(first, 0); }
 
-	public SkipIterator max() {
-		SkipSpan ss = stack.getEnd();
-		return new SkipIterator(ss, ss.nKeys - 1);
+	public SkipIterator<K, V> max() {
+		SkipSpan<K, V> ss = stack.getEnd();
+		return new SkipIterator<K, V>(ss, ss.nKeys - 1);
 	}
 
 	/** @return an iterator where nextKey() is the first one greater than or equal to 'key' */
-	public SkipIterator find(Comparable key) {
+	public SkipIterator<K, V> find(K key) {
 		int[] search = new int[1];
-		SkipSpan ss = stack.getSpan(stack.levels.length - 1, key, search);
+		SkipSpan<K, V> ss = stack.getSpan(stack.levels.length - 1, key, search);
 		if(search[0] < 0) { search[0] = -1 * (search[0] + 1); }
-		return new SkipIterator(ss, search[0]);
+		return new SkipIterator<K, V>(ss, search[0]);
 	}
 
 
diff --git a/core/java/src/net/metanotion/util/skiplist/SkipSpan.java b/core/java/src/net/metanotion/util/skiplist/SkipSpan.java
index 94210ebd4bc6d727b51322471761bb72cdacf588..71e9eb689f2d2df756a280a5705294df2b166f73 100644
--- a/core/java/src/net/metanotion/util/skiplist/SkipSpan.java
+++ b/core/java/src/net/metanotion/util/skiplist/SkipSpan.java
@@ -32,16 +32,16 @@ import java.io.Flushable;
 
 //import net.metanotion.io.block.BlockFile;
 
-public class SkipSpan implements Flushable {
+public class SkipSpan<K extends Comparable<? super K>, V> implements Flushable {
 	/** This is actually limited by BlockFile.spanSize which is much smaller */
 	public static final int MAX_SIZE = 256;
 
 	public int nKeys = 0;
-	public Comparable[] keys;
-	public Object[] vals;
-	public SkipSpan next, prev;
+	public K[] keys;
+	public V[] vals;
+	public SkipSpan<K, V> next, prev;
 
-	public SkipSpan newInstance(SkipList sl) { return new SkipSpan(keys.length); }
+	public SkipSpan<K, V> newInstance(SkipList<K, V> sl) { return new SkipSpan<K, V>(keys.length); }
 	public void killInstance() { }
 	public void flush() { }
 
@@ -53,8 +53,8 @@ public class SkipSpan implements Flushable {
 	public SkipSpan(int size) {
 		if(size < 1 || size > MAX_SIZE)
 			throw new IllegalArgumentException("Invalid span size " + size);
-		keys = new Comparable[size];
-		vals = new Object[size];
+		keys = (K[]) new Comparable[size];
+		vals = (V[]) new Object[size];
 	}
 
 	/** dumps all the data from here to the end */
@@ -70,7 +70,7 @@ public class SkipSpan implements Flushable {
 		return buf.toString();
 	}
 
-	private int binarySearch(Comparable key) {
+	private int binarySearch(K key) {
  		int high = nKeys - 1;
  		int low = 0;
  		int cur;
@@ -89,12 +89,12 @@ public class SkipSpan implements Flushable {
  		return (-1 * (low + 1));
 	}
 
-	public SkipSpan getEnd() {
+	public SkipSpan<K, V> getEnd() {
 		if(next == null) { return this; }
 		return next.getEnd();
 	}
 
-	public SkipSpan getSpan(Comparable key, int[] search) {
+	public SkipSpan<K, V> getSpan(K key, int[] search) {
 		if(nKeys == 0) {
 			search[0] = -1;
 			return this;
@@ -111,7 +111,7 @@ public class SkipSpan implements Flushable {
 		return this;
 	}
 
-	public Object get(Comparable key) {
+	public V get(K key) {
 		if(nKeys == 0) { return null; }
 		if(keys[nKeys - 1].compareTo(key) < 0) {
 			if(next == null) { return null; }
@@ -138,8 +138,8 @@ public class SkipSpan implements Flushable {
 		nKeys++;
 	}
 
-	private void split(int loc, Comparable key, Object val, SkipList sl) {
-		SkipSpan right = newInstance(sl);
+	private void split(int loc, K key, V val, SkipList<K, V> sl) {
+		SkipSpan<K, V> right = newInstance(sl);
 
 		if(this.next != null) { this.next.prev = right; }
 		right.next = this.next;
@@ -175,7 +175,7 @@ public class SkipSpan implements Flushable {
 	/**
 	 *  @return the new span if it caused a split, else null if it went in this span
 	 */
-	private SkipSpan insert(int loc, Comparable key, Object val, SkipList sl) {
+	private SkipSpan<K, V> insert(int loc, K key, V val, SkipList<K, V> sl) {
 		sl.addItem();
 		if(nKeys == keys.length) {
 			// split.
@@ -193,7 +193,7 @@ public class SkipSpan implements Flushable {
 	/**
 	 *  @return the new span if it caused a split, else null if it went in an existing span
 	 */
-	public SkipSpan put(Comparable key, Object val, SkipList sl)	{
+	public SkipSpan<K, V> put(K key, V val, SkipList<K, V> sl)	{
 		if(nKeys == 0) {
 			sl.addItem();
 			keys[0] = key;
@@ -246,7 +246,7 @@ public class SkipSpan implements Flushable {
 	 *          rv[1] is the deleted SkipSpan if the removed object was the last in the SkipSpan.
 	 *          rv is null if no object was removed.
 	 */
-	public Object[] remove(Comparable key, SkipList sl) {
+	public Object[] remove(K key, SkipList<K, V> sl) {
 		if(nKeys == 0) { return null; }
 		if(keys[nKeys - 1].compareTo(key) < 0) {
 			if(next == null) { return null; }
@@ -270,7 +270,7 @@ public class SkipSpan implements Flushable {
 				nKeys = next.nKeys;
 				//BlockFile.log.error("Killing next span " + next + ") and copying to this span " + this + " in remove of " + key);
 				// Make us point to next.next and him point back to us
-				SkipSpan nn = next.next;
+				SkipSpan<K, V> nn = next.next;
 				next.killInstance();
 				if (nn != null) {
 					nn.prev = this;
@@ -311,7 +311,7 @@ public class SkipSpan implements Flushable {
 	}
 
 	/** I2P */
-	public Comparable firstKey() {
+	public K firstKey() {
 		return keys[0];
 	}
 }