diff --git a/src/main/java/org/extendj/ragdoc/AstDeclParser.java b/src/main/java/org/extendj/ragdoc/AstDeclParser.java
index 538d79b0a6a703c3c615155fe5c223853513643f..9780c0393180719b598b589886d1cce5c1bd7560 100644
--- a/src/main/java/org/extendj/ragdoc/AstDeclParser.java
+++ b/src/main/java/org/extendj/ragdoc/AstDeclParser.java
@@ -122,7 +122,8 @@ public class AstDeclParser implements AutoCloseable, Closeable {
       if (in.peek() == ':') {
         accept(':');
         skipWhitespace();
-        String extendsName = parseName();
+        // allow generic type for tokens
+        String extendsName = parseName(true);
         component.add("e", extendsName);
         skipWhitespace();
       } else {
@@ -168,21 +169,38 @@ public class AstDeclParser implements AutoCloseable, Closeable {
   }
 
   private boolean isNameChar(int chr) {
-    return chr != EOF && (chr == '.' || Character.isLetterOrDigit(chr));
+     return chr != EOF && (chr == '.' || Character.isLetterOrDigit(chr) || chr == '_');
   }
 
   private String chrToStr(int chr) {
     return chr != EOF ? String.format("%c", chr) : "EOF";
   }
 
-  String parseName() throws IOException, JsonParser.SyntaxError {
+  private String parseName() throws IOException, JsonParser.SyntaxError {
+    return parseName(false);
+  }
+
+  private String parseName(boolean allowGeneric) throws IOException, JsonParser.SyntaxError {
     int next = in.peek();
     if (!isNameChar(next)) {
       throw new JsonParser.SyntaxError(
           String.format("Error: expected name or typename, found %s", chrToStr(next)));
     }
     StringBuilder name = new StringBuilder();
-    while (isNameChar(in.peek())) {
+    while (true) {
+      int peek = in.peek();
+      if (allowGeneric && peek == '<') {
+        // add everything until closing '>' is found, and then break
+        while (in.peek() != '>') {
+          name.append((char) in.pop());
+        }
+        // also add final '>'
+        name.append((char) in.pop());
+        break;
+      }
+      if (!isNameChar(peek)) {
+        break;
+      }
       name.append((char) in.pop());
     }
     return name.toString();
diff --git a/src/main/java/org/extendj/ragdoc/JsonBuilder.java b/src/main/java/org/extendj/ragdoc/JsonBuilder.java
index 34a16fe810a999c1d8ec0f56e2919048eec7fef2..d59c8dee259d4d75179eb464d152bb672fef97f9 100644
--- a/src/main/java/org/extendj/ragdoc/JsonBuilder.java
+++ b/src/main/java/org/extendj/ragdoc/JsonBuilder.java
@@ -402,7 +402,7 @@ public class JsonBuilder {
             JsonObject comp = c.object();
             if (!comp.get("e").stringValue("").isEmpty()) {
               comp.set("e",
-                  typeRef(type.lookupType(comp.get("e").stringValue("")).singletonValue()));
+                  typeRef(type.lookupType(stripGenericPart(comp.get("e").stringValue(""))).singletonValue()));
             }
           }
         } catch (IOException e) {
@@ -433,6 +433,11 @@ public class JsonBuilder {
     return obj;
   }
 
+  private String stripGenericPart(String s) {
+    int ltIndex = s.indexOf('<');
+    return ltIndex == -1 ? s : s.substring(0, ltIndex);
+  }
+
   /**
    * Registers subtype as being a subtype of type.
    */