diff --git a/build.sh b/build.sh
index 8de1b5776f40819a305762907ad316347afc2718..63f31a0df005942ec80000b28b82125853735837 100755
--- a/build.sh
+++ b/build.sh
@@ -50,6 +50,19 @@ if [ $? -ne 0 ]; then
         exit 1
 fi
 
+echo "" >> $LOG
+echo "-------------------------------" >> $LOG
+echo "compiling vipra-ui" | tee -a $LOG
+echo "-------------------------------" >> $LOG
+cd ./vipra-ui
+./build.sh >> $LOG 2>&1
+cd ..
+cp -r ./vipra-ui/public/* ./vipra-backend/src/main/webapp
+if [ $? -ne 0 ]; then
+        echo "error"
+        exit 1
+fi
+
 echo "" >> $LOG
 echo "-------------------------------" >> $LOG
 echo "compiling vipra-backend" | tee -a $LOG
diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java
index acbbf0a9ab25495d457c59b0f4662a8d1cbfe50a..de832f21cf8d821a796ea498249ae96d1bc91a7e 100644
--- a/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java
+++ b/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java
@@ -79,7 +79,6 @@ public class TopicResource {
 
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
-	@Consumes(MediaType.APPLICATION_JSON)
 	@Path("{id}")
 	public Response getTopic(@PathParam("id") String id, @QueryParam("fields") String fields)
 			throws ConfigException, IOException {
@@ -110,7 +109,6 @@ public class TopicResource {
 
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
-	@Consumes(MediaType.APPLICATION_JSON)
 	@Path("{id}/articles")
 	public Response getArticles(@PathParam("id") String id, @QueryParam("skip") Integer skip,
 			@QueryParam("limit") Integer limit, @QueryParam("sort") @DefaultValue("title") String sortBy,
@@ -138,6 +136,26 @@ public class TopicResource {
 		}
 	}
 
+	@GET
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("{id}/similar/by-words")
+	public Response similarTopicsByWords(@PathParam("id") String id, @QueryParam("skip") Integer skip,
+			@QueryParam("limit") Integer limit, @QueryParam("sort") @DefaultValue("title") String sortBy,
+			@QueryParam("fields") String fields) {
+		// TODO implement
+		return null;
+	}
+
+	@GET
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("{id}/similar/by-articles")
+	public Response similarTopicsByArticles(@PathParam("id") String id, @QueryParam("skip") Integer skip,
+			@QueryParam("limit") Integer limit, @QueryParam("sort") @DefaultValue("title") String sortBy,
+			@QueryParam("fields") String fields) {
+		// TODO implement
+		return null;
+	}
+
 	@PUT
 	@Consumes(MediaType.APPLICATION_JSON)
 	@Produces(MediaType.APPLICATION_JSON)
diff --git a/vipra-backend/src/main/webapp/WEB-INF/web.xml b/vipra-backend/src/main/webapp/WEB-INF/web.xml
index 90458a45e4c3c71a3e1d60ac21de704cec850c0f..d28582b4638dc6242b05aa7ad3d72027a8865922 100644
--- a/vipra-backend/src/main/webapp/WEB-INF/web.xml
+++ b/vipra-backend/src/main/webapp/WEB-INF/web.xml
@@ -1,15 +1,21 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
-  <servlet>
-    <servlet-name>jersey</servlet-name>
-    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
-    <init-param>
-      <param-name>javax.ws.rs.Application</param-name>
-      <param-value>de.vipra.rest.Application</param-value>
-    </init-param>
-  </servlet>
-  <servlet-mapping>
-    <servlet-name>jersey</servlet-name>
-    <url-pattern>/rest/*</url-pattern>
-  </servlet-mapping>
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
+	version="3.1">
+	<welcome-file-list>
+		<welcome-file>index.html</welcome-file>
+	</welcome-file-list>
+	<servlet>
+		<servlet-name>jersey</servlet-name>
+		<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+		<init-param>
+			<param-name>javax.ws.rs.Application</param-name>
+			<param-value>de.vipra.rest.Application</param-value>
+		</init-param>
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>jersey</servlet-name>
+		<url-pattern>/rest/*</url-pattern>
+	</servlet-mapping>
 </web-app>
\ No newline at end of file
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/CmdOptions.java b/vipra-cmd/src/main/java/de/vipra/cmd/CmdOptions.java
index 0610a0a427573eb4b68408745ce611f602f955a7..8158313b39dfe0da7a857ddfa62dd7870f83cbcb 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/CmdOptions.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/CmdOptions.java
@@ -41,6 +41,9 @@ public class CmdOptions extends Options {
 	public static final String OPT_MODELING = "m";
 	public static final String OPT_MODELING_LONG = "modeling";
 
+	public static final String OPT_INDEXING = "e";
+	public static final String OPT_INDEXING_LONG = "indexing";
+
 	public CmdOptions() {
 		addOption(Option.builder(OPT_HELP).longOpt(OPT_HELP_LONG).desc("print this message").build());
 		addOption(Option.builder(OPT_SHELL).longOpt(OPT_SHELL_LONG).hasArg(true).argName("name")
@@ -56,6 +59,7 @@ public class CmdOptions extends Options {
 		addOption(Option.builder(OPT_SILENT).longOpt(OPT_SILENT_LONG).desc("mute all output").build());
 		addOption(Option.builder(OPT_CONFIG).longOpt(OPT_CONFIG_LONG).desc("show configuration").build());
 		addOption(Option.builder(OPT_MODELING).longOpt(OPT_MODELING_LONG).desc("regenerate topic model").build());
+		addOption(Option.builder(OPT_INDEXING).longOpt(OPT_INDEXING_LONG).desc("regenerate search index").build());
 	}
 
 	public void printHelp(String cmd) {
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/Main.java b/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
index a883948673f8e76a1c787140d3a2467b70ea3214..70842fdfb932a45b41f6439d519b90e51d75afb4 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
@@ -11,6 +11,7 @@ import static de.vipra.cmd.CmdOptions.OPT_SHELL;
 import static de.vipra.cmd.CmdOptions.OPT_SILENT;
 import static de.vipra.cmd.CmdOptions.OPT_STATS;
 import static de.vipra.cmd.CmdOptions.OPT_TEST;
+import static de.vipra.cmd.CmdOptions.OPT_INDEXING;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -37,6 +38,7 @@ import de.vipra.cmd.option.ClearCommand;
 import de.vipra.cmd.option.Command;
 import de.vipra.cmd.option.ConfigCommand;
 import de.vipra.cmd.option.ImportCommand;
+import de.vipra.cmd.option.IndexingCommand;
 import de.vipra.cmd.option.ModelingCommand;
 import de.vipra.cmd.option.StatsCommand;
 import de.vipra.cmd.option.TestCommand;
@@ -112,6 +114,9 @@ public class Main {
 		if (cline.hasOption(OPT_MODELING))
 			commands.add(new ModelingCommand());
 
+		if (cline.hasOption(OPT_INDEXING))
+			commands.add(new IndexingCommand());
+
 		if (cline.hasOption(OPT_STATS))
 			commands.add(new StatsCommand());
 
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java b/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java
index 01cc8d15cddb786f91a45bac51a23b5be7a2eae8..1d8e0f205efadb14922cec6876b737c28ec3fe9a 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java
@@ -1,13 +1,8 @@
 package de.vipra.cmd.lda;
 
-import java.util.List;
-
 import de.vipra.cmd.ex.AnalyzerException;
 import de.vipra.util.Config;
-import de.vipra.util.ConvertStream;
 import de.vipra.util.WordMap;
-import de.vipra.util.model.TopicFull;
-import de.vipra.util.model.TopicRef;
 
 public abstract class Analyzer {
 
@@ -25,27 +20,6 @@ public abstract class Analyzer {
 
 	public abstract void analyze() throws AnalyzerException;
 
-	/**
-	 * Returns a converting stream of topics, read from the topic definition
-	 * file. Usually, a topic definition consists of a list of words, that are
-	 * assigned to that topic with a certain likeliness.
-	 * 
-	 * @return topic definition stream
-	 * @throws AnalyzerException
-	 */
-	public abstract ConvertStream<TopicFull> getTopicDefinitions() throws AnalyzerException;
-
-	/**
-	 * Returns a converting stream of lists of topic references. Normally, topic
-	 * modeling outputs topics for each word of each document. These references
-	 * are returned by this function.
-	 * 
-	 * @return stream of lists of topic references per document (ordered by
-	 *         index)
-	 * @throws AnalyzerException
-	 */
-	public abstract ConvertStream<List<TopicRef>> getTopics() throws AnalyzerException;
-
 	public static Analyzer getAnalyzer(Config config, WordMap wordMap) throws AnalyzerException {
 		Analyzer analyzer = null;
 		switch (config.analyzer) {
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/lda/DTMAnalyzer.java b/vipra-cmd/src/main/java/de/vipra/cmd/lda/DTMAnalyzer.java
index 54d2a8b82ab9fc171cb9551f826da8dd19501242..306ed2726d6d9db08aa9bc2bfa7b36b3a814e9c8 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/lda/DTMAnalyzer.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/lda/DTMAnalyzer.java
@@ -4,27 +4,26 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.util.List;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import de.vipra.cmd.ex.AnalyzerException;
-import de.vipra.cmd.file.DTMVocabulary;
 import de.vipra.util.Config;
 import de.vipra.util.Constants;
-import de.vipra.util.ConvertStream;
 import de.vipra.util.StringUtils;
 import de.vipra.util.WordMap;
 import de.vipra.util.ex.ConfigException;
-import de.vipra.util.model.TopicFull;
-import de.vipra.util.model.TopicRef;
 
 public class DTMAnalyzer extends Analyzer {
 
 	public static final Logger log = LogManager.getLogger(DTMAnalyzer.class);
 	public static final String NAME = "dtm";
 
+	public static final int dynamicMinIter = 100;
+	public static final int dynamicMaxIter = 1000;
+	public static final int staticIter = 100;
+
 	private String command;
 	private File modelDir;
 	private File outDir;
@@ -68,11 +67,11 @@ public class DTMAnalyzer extends Analyzer {
 				// alpha (default -10)
 				"--alpha=0.01",
 				// minimum number if iterations
-				"--lda_sequence_min_iter=5",
+				"--lda_sequence_min_iter=" + dynamicMinIter,
 				// maximum number of iterations
-				"--lda_sequence_max_iter=10",
+				"--lda_sequence_max_iter=" + dynamicMaxIter,
 				// em iter (default 20)
-				"--lda_max_em_iter=20",
+				"--lda_max_em_iter=" + staticIter,
 				// input file prefix
 				"--corpus_prefix=" + corpusPrefix,
 				// output directory
@@ -101,32 +100,10 @@ public class DTMAnalyzer extends Analyzer {
 			in.close();
 			p.waitFor();
 
-			log.info("done");
+			// TODO save model
 		} catch (IOException | InterruptedException e) {
 			throw new AnalyzerException(e);
 		}
 	}
 
-	@Override
-	public ConvertStream<TopicFull> getTopicDefinitions() throws AnalyzerException {
-		DTMVocabulary vocab;
-		try {
-			vocab = new DTMVocabulary(modelDir, false);
-		} catch (IOException e) {
-			throw new AnalyzerException(e);
-		}
-		return null;
-	}
-
-	@Override
-	public ConvertStream<List<TopicRef>> getTopics() throws AnalyzerException {
-		DTMVocabulary vocab;
-		try {
-			vocab = new DTMVocabulary(modelDir, false);
-		} catch (IOException e) {
-			throw new AnalyzerException(e);
-		}
-		return null;
-	}
-
 }
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/lda/JGibbAnalyzer.java b/vipra-cmd/src/main/java/de/vipra/cmd/lda/JGibbAnalyzer.java
index 8fa94370cfc54739ff0c6b3d6b4803a3fff82dc7..3af056a2d683fd06fb27bc17b88c84b5923bf60d 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/lda/JGibbAnalyzer.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/lda/JGibbAnalyzer.java
@@ -1,29 +1,38 @@
 package de.vipra.cmd.lda;
 
+import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileNotFoundException;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.bson.types.ObjectId;
 
 import de.vipra.cmd.ex.AnalyzerException;
+import de.vipra.cmd.file.FilebaseIndex;
 import de.vipra.util.Config;
 import de.vipra.util.Constants;
-import de.vipra.util.ConvertStream;
-import de.vipra.util.NumberUtils;
-import de.vipra.util.StringUtils;
+import de.vipra.util.CountMap;
+import de.vipra.util.FileUtils;
 import de.vipra.util.WordMap;
 import de.vipra.util.ex.ConfigException;
+import de.vipra.util.ex.DatabaseException;
+import de.vipra.util.model.ArticleFull;
+import de.vipra.util.model.Topic;
 import de.vipra.util.model.TopicFull;
 import de.vipra.util.model.TopicRef;
 import de.vipra.util.model.TopicWord;
 import de.vipra.util.model.Word;
+import de.vipra.util.service.MongoService;
 import jgibblda.Estimator;
 import jgibblda.LDACmdOption;
 
@@ -36,7 +45,9 @@ public class JGibbAnalyzer extends Analyzer {
 	private File modelDir;
 	private File modelFile;
 	private LDACmdOption options;
-	private WordMap wordMap;
+	private MongoService<ArticleFull, ObjectId> dbArticles;
+	private MongoService<TopicFull, ObjectId> dbTopics;
+	private FilebaseIndex index;
 
 	protected JGibbAnalyzer() {
 		super("JGibb Analyzer");
@@ -64,7 +75,14 @@ public class JGibbAnalyzer extends Analyzer {
 
 		options.modelName = NAME;
 
-		this.wordMap = wordMap;
+		try {
+			config = Config.getConfig();
+			dbArticles = MongoService.getDatabaseService(config, ArticleFull.class);
+			dbTopics = MongoService.getDatabaseService(config, TopicFull.class);
+			index = new FilebaseIndex(new File(modelDir, "index"));
+		} catch (Exception e) {
+			throw new AnalyzerException(e);
+		}
 	}
 
 	@Override
@@ -75,103 +93,88 @@ public class JGibbAnalyzer extends Analyzer {
 		Estimator estimator = new Estimator();
 		estimator.init(options);
 		estimator.estimate();
-	}
 
-	@Override
-	public ConvertStream<TopicFull> getTopicDefinitions() throws AnalyzerException {
+		// read topic definitions and save
+
 		File twords = new File(modelDir, NAME + ".twords");
+		List<String> lines;
 		try {
-			return new ConvertStream<TopicFull>(twords) {
-				@Override
-				public TopicFull convert(String line) {
-					TopicFull topicDef = new TopicFull();
-					List<TopicWord> topicWords = new ArrayList<>();
-
-					// get index of topic
-					// the index will be used as a temporary id until the topic
-					// is created in the database
-					Integer index = StringUtils.getFirstNumber(line);
-					if (index == null) {
-						log.error("could not extract topic index from line: " + line);
-					} else {
-						topicDef.setIndex(index);
-					}
-
-					// get all lines that follow until the next topic is
-					// discovered (line does not start with a tab). Cut off
-					// after k words are gathered.
-					String nextLine;
-					while ((nextLine = nextLine()) != null) {
-						if (nextLine.startsWith("\t")) {
-							String[] parts = nextLine.trim().split("\\s+");
-							try {
-								Word word = wordMap.get(parts[0]);
-								// round likeliness precision
-								double likeliness = NumberUtils.roundToPrecision(Double.parseDouble(parts[1]),
-										Constants.LIKELINESS_PRECISION);
-
-								// check if word likely enough to relate to
-								// topic
-								if (likeliness < Constants.MINIMUM_LIKELINESS)
-									continue;
-
-								TopicWord topicWord = new TopicWord(word, likeliness);
-								topicWords.add(topicWord);
-							} catch (NumberFormatException e) {
-								log.error("could not parse number in line: " + nextLine);
-							} catch (ArrayIndexOutOfBoundsException e) {
-								log.error("ill formatted topic definition in line: " + nextLine);
-							}
-						} else {
-							buffer(nextLine);
-							break;
-						}
-					}
-					Collections.sort(topicWords, Collections.reverseOrder());
-					topicDef.setWords(topicWords);
-					topicDef.setName(TopicFull.getNameFromWords(topicWords));
-					return topicDef;
-				}
-			};
-		} catch (FileNotFoundException e) {
+			lines = FileUtils.readFile(twords);
+		} catch (IOException e) {
 			throw new AnalyzerException(e);
 		}
-	}
 
-	@Override
-	public ConvertStream<List<TopicRef>> getTopics() throws AnalyzerException {
+		List<TopicFull> newTopics = new ArrayList<>(options.K);
+		Map<Integer, Topic> newTopicsMap = new HashMap<>(options.K);
+
+		// for each topic
+		for (int topicNum = 0; topicNum < options.K; topicNum++) {
+			TopicFull newTopic = new TopicFull();
+			List<TopicWord> topicWords = new ArrayList<>(options.twords);
+
+			// for each word
+			for (int wordNum = 0, lineNum = topicNum * options.K + 1; wordNum < options.twords; wordNum++, lineNum++) {
+				String[] parts = lines.get(lineNum).trim().split("\\s+");
+				TopicWord topicWord = new TopicWord(new Word(parts[0]), Double.parseDouble(parts[1]));
+				topicWords.add(topicWord);
+			}
+
+			newTopic.setWords(topicWords);
+			newTopics.add(newTopic);
+			newTopicsMap.put(topicNum, new Topic(newTopic.getId()));
+		}
+
+		dbTopics.drop();
+		try {
+			dbTopics.createMultiple(newTopics);
+		} catch (DatabaseException e) {
+			throw new AnalyzerException(e);
+		}
+
+		// read documents and reference topics
+
 		File tassign = new File(modelDir, NAME + ".tassign");
+		Pattern topicIndexPattern = Pattern.compile(":(\\d+)");
+		BufferedReader in = null;
+
 		try {
-			return new ConvertStream<List<TopicRef>>(tassign) {
-				@Override
-				public List<TopicRef> convert(String line) {
-					// count topics
-					Map<String, Integer> countMap = new HashMap<>();
-					String[] wordList = line.split("\\s+");
-					for (String word : wordList) {
-						// the file uses a simple integer as topic ids. Use this
-						// id as a temporary id until the topic is created in
-						// the database
-						String topic = word.split(":")[1];
-						Integer count = countMap.get(topic);
-						countMap.put(topic, count == null ? 1 : count + 1);
-					}
-
-					// turn into list
-					List<TopicRef> topicCount = new ArrayList<>(countMap.size());
-					for (Entry<String, Integer> e : countMap.entrySet()) {
-						TopicRef tc = new TopicRef();
-						tc.setTopicIndex(e.getKey());
-						tc.setCount(e.getValue());
-						topicCount.add(tc);
-					}
-
-					Collections.sort(topicCount, Collections.reverseOrder());
-
-					return topicCount;
+			in = new BufferedReader(new InputStreamReader(new FileInputStream(tassign)));
+			String line;
+			int articleIndex = 0;
+
+			// each line in the tassign file is a document, formatted with
+			// <word-id>:<topic-id>
+			while ((line = in.readLine()) != null) {
+				// extract topic ids and count them
+				CountMap<String> countMap = new CountMap<>();
+				Matcher matcher = topicIndexPattern.matcher(line);
+				while (matcher.find()) {
+					countMap.count(matcher.group(1));
+				}
+
+				// create list of topics refs referencing topics with counted
+				// occurrences
+				List<TopicRef> newTopicRefs = new ArrayList<>();
+				for (Entry<String, Integer> entry : countMap.entrySet()) {
+					TopicRef ref = new TopicRef();
+					ref.setCount(entry.getValue());
+					ref.setTopic(newTopicsMap.get(Integer.parseInt(entry.getKey())));
+					newTopicRefs.add(ref);
+				}
+
+				// update article with topic references (partial update)
+				ArticleFull article = new ArticleFull();
+				article.setId(index.get(articleIndex++));
+				article.setTopics(newTopicRefs);
+				try {
+					// TODO: using field name here. Hard to refactor
+					dbArticles.updateSingle(article, "topics");
+				} catch (DatabaseException e) {
+					log.error(e);
 				}
-			};
-		} catch (FileNotFoundException e) {
+			}
+			in.close();
+		} catch (IOException e) {
 			throw new AnalyzerException(e);
 		}
 	}
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..29977e57cc16f76126474999bbf89194d79fd2e5
--- /dev/null
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java
@@ -0,0 +1,54 @@
+package de.vipra.cmd.option;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bson.types.ObjectId;
+import org.elasticsearch.client.Client;
+
+import de.vipra.cmd.file.Filebase;
+import de.vipra.cmd.file.FilebaseIndex;
+import de.vipra.util.Config;
+import de.vipra.util.ESClient;
+import de.vipra.util.ESSerializer;
+import de.vipra.util.MongoUtils;
+import de.vipra.util.model.ArticleFull;
+import de.vipra.util.service.MongoService;
+
+public class IndexingCommand implements Command {
+
+	public static final Logger log = LogManager.getLogger(IndexingCommand.class);
+
+	@Override
+	public void run() throws Exception {
+		Config config = Config.getConfig();
+		MongoService<ArticleFull, ObjectId> dbArticles = MongoService.getDatabaseService(config, ArticleFull.class);
+		Filebase filebase = Filebase.getFilebase(config);
+		FilebaseIndex index = filebase.getIndex();
+		Client elasticClient = ESClient.getClient(config);
+		ESSerializer<ArticleFull> elasticSerializer = new ESSerializer<>(ArticleFull.class);
+
+		// clear index
+		elasticClient.admin().indices().prepareDelete("_all").get();
+
+		Iterator<String> indexIter = index.iterator();
+		while (indexIter.hasNext()) {
+			// get article from database
+			String id = indexIter.next();
+			ArticleFull article = dbArticles.getSingle(MongoUtils.objectId(id), true);
+			if (article == null) {
+				log.error("no article found in db for id " + id);
+				continue;
+			}
+
+			// index article
+			Map<String, Object> source = elasticSerializer.serialize(article);
+			elasticClient.prepareIndex("articles", "article", article.getId().toString()).setSource(source).get();
+		}
+
+		elasticClient.close();
+	}
+
+}
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java
index 0bb226b5828f0cf59488a70a4ce94c63eb941058..fd5a5a6e68371b7abe63509a21f6487d3d5861c0 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java
@@ -1,35 +1,13 @@
 package de.vipra.cmd.option;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Set;
-
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.bson.types.ObjectId;
-import org.elasticsearch.client.Client;
 
-import de.vipra.cmd.file.Filebase;
-import de.vipra.cmd.file.FilebaseIndex;
 import de.vipra.cmd.lda.Analyzer;
 import de.vipra.util.Config;
-import de.vipra.util.Constants;
-import de.vipra.util.ConvertStream;
-import de.vipra.util.ESClient;
-import de.vipra.util.ESSerializer;
-import de.vipra.util.MongoUtils;
 import de.vipra.util.StringUtils;
 import de.vipra.util.Timer;
 import de.vipra.util.WordMap;
-import de.vipra.util.ex.DatabaseException;
-import de.vipra.util.model.ArticleFull;
-import de.vipra.util.model.Topic;
-import de.vipra.util.model.TopicFull;
-import de.vipra.util.model.TopicRef;
 import de.vipra.util.model.Word;
 import de.vipra.util.service.MongoService;
 
@@ -38,26 +16,16 @@ public class ModelingCommand implements Command {
 	public static final Logger log = LogManager.getLogger(ModelingCommand.class);
 
 	private Config config;
-	private MongoService<ArticleFull, ObjectId> dbArticles;
-	private MongoService<TopicFull, ObjectId> dbTopics;
 	private MongoService<Word, String> dbWords;
-	private Filebase filebase;
 	private WordMap wordMap;
 	private Analyzer analyzer;
-	private Client elasticClient;
-	private ESSerializer<ArticleFull> elasticSerializer;
 
 	@Override
 	public void run() throws Exception {
 		config = Config.getConfig();
-		dbArticles = MongoService.getDatabaseService(config, ArticleFull.class);
-		dbTopics = MongoService.getDatabaseService(config, TopicFull.class);
 		dbWords = MongoService.getDatabaseService(config, Word.class);
-		filebase = Filebase.getFilebase(config);
 		wordMap = new WordMap(dbWords);
 		analyzer = Analyzer.getAnalyzer(config, wordMap);
-		elasticClient = ESClient.getClient(config);
-		elasticSerializer = new ESSerializer<>(ArticleFull.class);
 
 		log.info("using analyzer: " + analyzer.getName());
 
@@ -71,94 +39,9 @@ public class ModelingCommand implements Command {
 		analyzer.analyze();
 		timer.lap("topic modeling");
 
-		/*
-		 * save topic model
-		 */
-		log.info("saving topic definitions");
-		int batchSize = 100;
-		ConvertStream<TopicFull> topicDefs = analyzer.getTopicDefinitions();
-		Map<String, TopicFull> topicIndexMap = new HashMap<>();
-		dbTopics.drop();
-		List<TopicFull> newTopicDefs = new ArrayList<>(batchSize);
-		List<Topic> newTopicRefs = new ArrayList<>();
-		Iterator<TopicFull> it = topicDefs.iterator();
-		while (it.hasNext()) {
-			newTopicDefs.add(it.next());
-			if (newTopicDefs.size() == batchSize || !it.hasNext()) {
-				dbTopics.createMultiple(newTopicDefs);
-				for (TopicFull newTopicDef : newTopicDefs) {
-					topicIndexMap.put(Integer.toString(newTopicDef.getIndex()), newTopicDef);
-					newTopicRefs.add(new Topic(newTopicDef.getId()));
-				}
-				newTopicDefs.clear();
-			}
-		}
-		timer.lap("saving topics");
-
-		/*
-		 * save topic refs and index article
-		 */
-		log.info("saving document topics");
-		ConvertStream<List<TopicRef>> topicStream = analyzer.getTopics();
-		FilebaseIndex index = filebase.getIndex();
-		Iterator<String> indexIter = index.iterator();
-		Iterator<List<TopicRef>> topicRefsListIter = topicStream.iterator();
-		elasticClient.admin().indices().prepareDelete("_all").get();
-		while (indexIter.hasNext() && topicRefsListIter.hasNext()) {
-			// get article from database
-			String id = indexIter.next();
-			ArticleFull article = dbArticles.getSingle(MongoUtils.objectId(id), true);
-			if (article == null) {
-				log.error("no article found in db for id " + id);
-				continue;
-			}
-
-			double wordCount = article.getStats().getWordCount();
-
-			// insert topic references into article, ignoring low refs
-			List<TopicRef> topicRefs = topicRefsListIter.next();
-			for (ListIterator<TopicRef> topicRefsIter = topicRefs.listIterator(); topicRefsIter.hasNext();) {
-				TopicRef topicRef = topicRefsIter.next();
-				if ((topicRef.getCount() / wordCount) < Constants.TOPIC_THRESHOLD) {
-					topicRefsIter.remove();
-					continue;
-				}
-				TopicFull topicFull = topicIndexMap.get(topicRef.getTopicIndex());
-				if (topicFull != null) {
-					topicRef.setTopic(new Topic(topicFull.getId(), topicFull.getName()));
-				} else
-					log.error("no object id for topic index " + topicRef.getTopicIndex());
-			}
-
-			article.setTopics(topicRefs);
-
-			try {
-				dbArticles.replaceSingle(article);
-			} catch (DatabaseException e) {
-				log.error("could not update article: " + article.getTitle() + " (" + article.getId() + ")");
-			}
-
-			// index article
-			Map<String, Object> source = elasticSerializer.serialize(article);
-			elasticClient.prepareIndex("articles", "article", article.getId().toString()).setSource(source).get();
-		}
-
-		/*
-		 * save words
-		 */
-		log.info("saving words");
-		Set<Word> importedWords = wordMap.getNewWords();
-		timer.lap("saving topic refs and indexing");
-		wordMap.create();
-		timer.lap("saving words");
-
-		elasticClient.close();
-
 		/*
 		 * run information
 		 */
-		int newWordsCount = importedWords.size();
-		log.info("imported " + newWordsCount + " new " + StringUtils.quantity(newWordsCount, "word"));
 		log.info(timer.toString());
 		log.info("done in " + StringUtils.timeString(timer.total()));
 	}
diff --git a/vipra-cmd/src/main/resources/config.properties b/vipra-cmd/src/main/resources/config.properties
index ca312cfdab39ca456a9fd539ec23088496d59dac..01b9444d6837e0520e7f4edf60af96e78bb7c300 100644
--- a/vipra-cmd/src/main/resources/config.properties
+++ b/vipra-cmd/src/main/resources/config.properties
@@ -3,5 +3,4 @@ db.port=27017
 db.name=test
 tm.processor=corenlp
 tm.analyzer=jgibb
-tm.saveallwords=false
-tm.dtmpath=/home/eike/Downloads/dtm_release/dtm/main
\ No newline at end of file
+tm.dtmpath=/home/eike/repos/master/dtm_release/dtm/main
\ No newline at end of file
diff --git a/vipra-ui/app/html/about.html b/vipra-ui/app/html/about.html
index edd025ffe06df95478efccaa5c932c837f3ae770..d84cf03509d831517129befc3076fbca0c7f7df7 100644
--- a/vipra-ui/app/html/about.html
+++ b/vipra-ui/app/html/about.html
@@ -1,4 +1,4 @@
-<div ng-cloak>
+<div ng-cloak ng-hide="$state.current.name !== 'about'">
   <div class="page-header">
     <h1>About</h1>
   </div>
@@ -261,4 +261,6 @@
       </tr>
     </tbody>
   </table>
-</div>
\ No newline at end of file
+</div>
+
+<div ng-cloak ui-view></div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/articles/index.html b/vipra-ui/app/html/articles/index.html
index 103bfb96843f75b14e4cf03b0e073e50997fe6f5..f30bfbbc44e085411a99750e1e910c0abd1d3a3f 100644
--- a/vipra-ui/app/html/articles/index.html
+++ b/vipra-ui/app/html/articles/index.html
@@ -1,4 +1,4 @@
-<div ng-cloak>
+<div ng-cloak ng-hide="$state.current.name !== 'articles'">
   <div class="text-muted">
     Found <ng-pluralize count="articlesTotal||0" when="{0:'no articles',1:'1 article',other:'{} articles'}"></ng-pluralize> in the database.
     <span ng-show="articlesTotal">
@@ -26,5 +26,7 @@
     </li>
   </ol>
 
-  <pagination total="articlesTotal" page="page" limit="limit" change="changePage"/>
-</div>
\ No newline at end of file
+  <pagination total="articlesTotal" page="page" limit="limit"/>
+</div>
+
+<div ng-cloak ui-view></div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/articles/show.html b/vipra-ui/app/html/articles/show.html
index 27afa34735a1fe8eba0d27a8b4f4c62782560564..fe66fe1eaafb118c581229df35df1bc67a2f0c22 100644
--- a/vipra-ui/app/html/articles/show.html
+++ b/vipra-ui/app/html/articles/show.html
@@ -1,4 +1,4 @@
-<div ng-cloak>
+<div ng-cloak ng-hide="$state.current.name !== 'articles.show'">
   <div class="page-header">
     <h1 ng-bind="::article.title"></h1>
 
@@ -82,4 +82,6 @@
 
   <hr>
   <p ng-bind-html="::article.text" class="text-justify"></p>
-</div>
\ No newline at end of file
+</div>
+
+<div ng-cloak ui-view></div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/index.html b/vipra-ui/app/html/index.html
index cbbe697bc86ad5b52503860aa688b10e81a4aa4b..833c9fccfcd6fe90b73a8fc4ac6da439044cf08f 100644
--- a/vipra-ui/app/html/index.html
+++ b/vipra-ui/app/html/index.html
@@ -1,4 +1,4 @@
-<div ng-cloak>
+<div ng-cloak ng-hide="$state.current.name !== 'index'">
   <div class="row" ng-hide="search">
     <div class="col-md-12">
       <div class="heading"></div>
@@ -59,4 +59,6 @@
       </ul>
     </div>
   </div>
-</div>
\ No newline at end of file
+</div>
+
+<div ng-cloak ui-view></div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/modal.html b/vipra-ui/app/html/modal.html
deleted file mode 100644
index e441d0bf66c3142a7db352366f576f175590b6bf..0000000000000000000000000000000000000000
--- a/vipra-ui/app/html/modal.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<div class="modal-header">
-    <h3 class="modal-title">I'm a modal!</h3>
-</div>
-
-<div class="modal-body">
-</div>
-
-<div class="modal-footer">
-    <button class="btn btn-primary" type="button" ng-click="ok()">OK</button>
-    <button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>
-</div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/network.html b/vipra-ui/app/html/network.html
index b05ce3981550f677285fce56df92b31528175799..431188c673e36bff7cbeac9a8f128fe7e4cafeac 100644
--- a/vipra-ui/app/html/network.html
+++ b/vipra-ui/app/html/network.html
@@ -1,4 +1,4 @@
-<div ng-cloak>
+<div ng-cloak ng-hide="$state.current.name !== 'network'">
   <div class="fullsize navpadding">
     <div class="graph-legend overlay">
       <label style="color:{{colors.articles}}">
@@ -13,4 +13,6 @@
     </div>
     <div class="fullsize navpadding" id="visgraph"></div>
   </div>
-</div>
\ No newline at end of file
+</div>
+
+<div ng-cloak ui-view></div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/topics/articles.html b/vipra-ui/app/html/topics/articles.html
new file mode 100644
index 0000000000000000000000000000000000000000..2818921cda1a2cf80db1614a12885872bcc802a5
--- /dev/null
+++ b/vipra-ui/app/html/topics/articles.html
@@ -0,0 +1,44 @@
+<div ng-cloak ng-hide="$state.current.name !== 'topics.show.articles'">
+  <div class="page-header">
+    <h1 ng-bind-template="Articles for topic '{{::topic.name}}'"></h1>
+
+    <table class="item-actions">
+      <tr>
+        <td>
+          <a class="btn btn-default" ui-sref="^">Back</a>
+        </td>
+      </tr>
+    </table>
+  </div>
+  
+  <div class="text-muted">
+    Found <ng-pluralize count="articlesTotal||0" when="{0:'no articles',1:'1 article',other:'{} articles'}"></ng-pluralize> in the database.
+    <span ng-show="articlesTotal">
+      Sort by
+      <ol class="nya-bs-select nya-bs-condensed" ng-model="sort" ng-model-store="sort" ng-model-default="'date'">
+        <li value="title" class="nya-bs-option"><a>Title</a></li>
+        <li value="date" class="nya-bs-option"><a>Date</a></li>
+        <li value="created" class="nya-bs-option"><a>Added</a></li>
+      </ol>
+      Direction
+      <ol class="nya-bs-select nya-bs-condensed" ng-model="order" ng-model-store="order" ng-model-default="'+'">
+        <li value="+" class="nya-bs-option"><a>Ascending</a></li>
+        <li value="-" class="nya-bs-option"><a>Descending</a></li>
+      </ol>
+    </span>
+    <br>
+    Page <span ng-bind="page||1"></span> of <span ng-bind="maxPage||1"></span>.
+  </div>
+
+  <pagination total="articlesTotal" page="page" limit="limit" change="changePage"/>
+
+  <ol ng-attr-start="{{(page-1)*limit+1}}">
+    <li ng-repeat="article in articles">
+      <a ui-sref="articles.show({id: article.id})" ng-bind="::article.title"></a>
+    </li>
+  </ol>
+
+  <pagination total="articlesTotal" page="page" limit="limit"/>
+</div>
+
+<div ng-cloak ui-view></div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/topics/index.html b/vipra-ui/app/html/topics/index.html
index 8d5a249654d34abc170ce3b082260889ad84a9b0..b4273145dfc78a05af30d7a6e22f19a8c7627f48 100644
--- a/vipra-ui/app/html/topics/index.html
+++ b/vipra-ui/app/html/topics/index.html
@@ -1,4 +1,4 @@
-<div ng-cloak>
+<div ng-cloak ng-hide="$state.current.name !== 'topics'">
   <div class="text-muted">
     Found <ng-pluralize count="topicsTotal||0" when="{0:'no topics',1:'1 topic',other:'{} topics'}"></ng-pluralize> in the database.
     <span ng-show="topicsTotal">
@@ -25,5 +25,7 @@
     </li>
   </ol>
 
-  <pagination total="topicsTotal" page="page" limit="limit" change="changePage"/>
-</div>
\ No newline at end of file
+  <pagination total="topicsTotal" page="page" limit="limit"/>
+</div>
+
+<div ng-cloak ui-view></div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/topics/show.html b/vipra-ui/app/html/topics/show.html
index 964d974382856a7a1d3d1c6d998e644e76daf2ec..2875d8c765214019dfe568660fd63c7425078340 100644
--- a/vipra-ui/app/html/topics/show.html
+++ b/vipra-ui/app/html/topics/show.html
@@ -1,4 +1,4 @@
-<div ng-cloak>
+<div ng-cloak ng-hide="$state.current.name !== 'topics.show'">
   <div class="page-header">
     <h1>
       <div ng-bind="topic.name" ng-hide="isRename"></div>
@@ -29,6 +29,15 @@
         <td>
           <a class="btn btn-default" ui-sref="network({type:'topics', id:topic.id})">Network graph</a>
         </td>
+        <td>
+          <a class="btn btn-default" ui-sref="topics.show.articles({id:topic.id})">Articles</a>
+        </td>
+        <td>
+          <bs-dropdown label="Similar Topics">
+            <li><a ui-sref="topics.show.similar({id:topic.id, type:'by-words'})">By word share</a></li>
+            <li><a ui-sref="topics.show.similar({id:topic.id, type:'by-articles'})">By article share</a></li>
+          </bs-dropdown>
+        </td>
       </tr>
     </table>
   </div>
@@ -84,4 +93,6 @@
       </table>
     </div>
   </div>
-</div>
\ No newline at end of file
+</div>
+
+<div ng-cloak ui-view></div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/topics/similar.html b/vipra-ui/app/html/topics/similar.html
new file mode 100644
index 0000000000000000000000000000000000000000..e868dc6e1f8aa1b4e8685edf84d8f9826297a6fa
--- /dev/null
+++ b/vipra-ui/app/html/topics/similar.html
@@ -0,0 +1,5 @@
+<div ng-cloak ng-hide="$state.current.name !== 'topics.show.similar'">
+
+</div>
+
+<div ng-cloak ui-view></div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/words/index.html b/vipra-ui/app/html/words/index.html
index 5da54c2da1f787f3e4e2ed9686bc80fc63c52923..d89dba089efdb583f7b3145e8762c685205510a7 100644
--- a/vipra-ui/app/html/words/index.html
+++ b/vipra-ui/app/html/words/index.html
@@ -1,4 +1,4 @@
-<div ng-cloak>
+<div ng-cloak ng-hide="$state.current.name !== 'words'">
   <div class="text-muted">
     Found <ng-pluralize count="wordsTotal||0" when="{0:'no words',1:'1 word',other:'{} words'}"></ng-pluralize> in the database.
     <span ng-show="wordsTotal">
@@ -43,5 +43,7 @@
     </div>
   </div>
 
-  <pagination total="wordsTotal" page="page" limit="limit" change="changePage"/>
-</div>
\ No newline at end of file
+  <pagination total="wordsTotal" page="page" limit="limit"/>
+</div>
+
+<div ng-cloak ui-view></div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/words/show.html b/vipra-ui/app/html/words/show.html
index 891b8f3d74e1bf00e2fa551ca1024aa3b633accd..d1ca723b17d991032cbd7df8eb0819cc4689f21c 100644
--- a/vipra-ui/app/html/words/show.html
+++ b/vipra-ui/app/html/words/show.html
@@ -1,4 +1,4 @@
-<div ng-cloak>
+<div ng-cloak ng-hide="$state.current.name !== 'words.show'">
   <div class="page-header">
     <h1 ng-bind="::word.id"></h1>
   </div>
@@ -29,4 +29,6 @@
       </ol>
     </div>
   </div>
-</div>
\ No newline at end of file
+</div>
+
+<div ng-cloak ui-view></div>
\ No newline at end of file
diff --git a/vipra-ui/app/index.html b/vipra-ui/app/index.html
index b4995c130191a52e091396f76f60039060c5372f..dbb7057b110438f78add64361f9c41fbd51d8ad7 100644
--- a/vipra-ui/app/index.html
+++ b/vipra-ui/app/index.html
@@ -35,7 +35,7 @@
     <script src="js/templates.js"></script>
   </head>
   <body>
-    <nav class="navbar navbar-default navbar-static-top">
+    <nav class="navbar navbar-default navbar-static-top navbar-breadcrumbs">
       <div class="container-fluid">
         <!-- Brand and toggle get grouped for better mobile display -->
         <div class="navbar-header">
@@ -73,6 +73,8 @@
       </div><!-- /.container-fluid -->
     </nav>
 
+    <div ncy-breadcrumb ng-hide="$state.current.name === 'index'"></div>
+
     <div class="container" ui-view ng-cloak></div>
   </body>
 </html>
\ No newline at end of file
diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js
index bfb10682a41c91bacb933c2e6ac5c6d34ac1efaf..f348a0cbc29534af05d49b58093e8ee2484b0fa5 100644
--- a/vipra-ui/app/js/app.js
+++ b/vipra-ui/app/js/app.js
@@ -10,6 +10,7 @@
     'ngWebSocket',
     'ui.router',
     'nya.bootstrap.select',
+    'ncy-angular-breadcrumb',
     'vipra.controllers',
     'vipra.directives',
     'vipra.factories',
@@ -25,37 +26,48 @@
     
     $stateProvider.state('index', {
       url: '/',
-      templateUrl: Vipra.const.tplBase + '/index.html',
-      controller: 'IndexController'
+      templateUrl: 'html/index.html',
+      controller: 'IndexController',
+      ncyBreadcrumb: {
+        label: 'Start'
+      }
     });
 
     $stateProvider.state('about', {
       url: '/about',
-      templateUrl: Vipra.const.tplBase + '/about.html',
-      controller: 'AboutController'
+      templateUrl: 'html/about.html',
+      controller: 'AboutController',
+      ncyBreadcrumb: {
+        label: 'About'
+      }
     });
 
     $stateProvider.state('network', {
       url: '/network/:type/:id',
-      templateUrl: Vipra.const.tplBase + '/network.html',
-      controller: 'NetworkController'
+      templateUrl: 'html/network.html',
+      controller: 'NetworkController',
+      ncyBreadcrumb: {
+        label: 'Network'
+      }
     });
 
     // states: articles
 
     $stateProvider.state('articles', {
       url: '/articles',
-      templateUrl: Vipra.const.tplBase + '/articles/index.html',
-      controller: 'ArticlesIndexController'
+      templateUrl: 'html/articles/index.html',
+      controller: 'ArticlesIndexController',
+      ncyBreadcrumb: {
+        label: 'Articles'
+      }
     });
 
     $stateProvider.state('articles.show', {
       url: '/:id',
-      views: {
-        "@": {
-          templateUrl: Vipra.const.tplBase + '/articles/show.html',
-          controller: 'ArticlesShowController'
-        }
+      templateUrl: 'html/articles/show.html',
+      controller: 'ArticlesShowController',
+      ncyBreadcrumb: {
+        label: '{{article.title.ellipsize(30)}}'
       }
     });
 
@@ -63,17 +75,37 @@
 
     $stateProvider.state('topics', {
       url: '/topics',
-      templateUrl: Vipra.const.tplBase + '/topics/index.html',
-      controller: 'TopicsIndexController'
+      templateUrl: 'html/topics/index.html',
+      controller: 'TopicsIndexController',
+      ncyBreadcrumb: {
+        label: 'Topics'
+      }
     });
 
     $stateProvider.state('topics.show', {
       url: '/:id',
-      "views": {
-        "@": {
-          templateUrl: Vipra.const.tplBase + '/topics/show.html',
-          controller: 'TopicsShowController'
-        }
+      templateUrl: 'html/topics/show.html',
+      controller: 'TopicsShowController',
+      ncyBreadcrumb: {
+        label: '{{topic.name}}'
+      }
+    });
+
+    $stateProvider.state('topics.show.articles', {
+      url: '/articles',
+      templateUrl: 'html/topics/articles.html',
+      controller: 'TopicsArticlesController',
+      ncyBreadcrumb: {
+        label: 'Topic Articles'
+      }
+    });
+
+    $stateProvider.state('topics.show.similar', {
+      url: '/similar/:type',
+      templateUrl: 'html/topics/similar.html',
+      controller: 'TopicsSimilarController',
+      ncyBreadcrumb: {
+        label: 'Similar Topics (by {{typeLabel}})'
       }
     });
 
@@ -81,17 +113,19 @@
 
     $stateProvider.state('words', {
       url: '/words',
-      templateUrl: Vipra.const.tplBase + '/words/index.html',
-      controller: 'WordsIndexController'
+      templateUrl: 'html/words/index.html',
+      controller: 'WordsIndexController',
+      ncyBreadcrumb: {
+        label: 'Words'
+      }
     });
 
     $stateProvider.state('words.show', {
       url: '/:id',
-      "views": {
-        "@": {
-          templateUrl: Vipra.const.tplBase + '/words/show.html',
-          controller: 'WordsShowController'
-        }
+      templateUrl: 'html/words/show.html',
+      controller: 'WordsShowController',
+      ncyBreadcrumb: {
+        label: '{{word.id}}'
       }
     });
 
diff --git a/vipra-ui/app/js/config.js b/vipra-ui/app/js/config.js
index 643b5e335d4b33eaea31940ee2c5ba76725c47b0..e6f400bdc4fce283becb170cc122199f6b995a1c 100644
--- a/vipra-ui/app/js/config.js
+++ b/vipra-ui/app/js/config.js
@@ -4,7 +4,10 @@
 
   Vipra.config = {
     restUrl: '//' + location.hostname + ':8000/vipra/rest',
-    websocketUrl: 'ws://' + location.hostname + ':8000/vipra/ws'
+    websocketUrl: 'ws://' + location.hostname + ':8000/vipra/ws',
+    latestItems: 3,
+    searchResults: 10,
+    pageSize: 100
   };
 
 })();
\ No newline at end of file
diff --git a/vipra-ui/app/js/constants.js b/vipra-ui/app/js/constants.js
deleted file mode 100644
index b60fe8af4e2d41e875fc7bf7a33b7bb6cda0fb65..0000000000000000000000000000000000000000
--- a/vipra-ui/app/js/constants.js
+++ /dev/null
@@ -1,12 +0,0 @@
-(function() {
-
-  window.Vipra = window.Vipra || {};
-
-  Vipra.const = {
-    tplBase: 'html',
-    latestItems: 3,
-    searchResults: 10,
-    pageSize: 100
-  };
-
-})();
\ No newline at end of file
diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js
index 5fa9f2e55136d44d10ea17369a97697399aa06c9..daa5d471b04c7e2f80328aa6d625323760ea07d1 100644
--- a/vipra-ui/app/js/controllers.js
+++ b/vipra-ui/app/js/controllers.js
@@ -21,15 +21,15 @@
 
     $scope.search = $location.search().query;
 
-    ArticleFactory.query({limit:Vipra.const.latestItems, sort:'-created'}, function(data) {
+    ArticleFactory.query({limit:Vipra.config.latestItems, sort:'-created'}, function(data) {
       $scope.latestArticles = data;
     });
 
-    TopicFactory.query({limit:Vipra.const.latestItems, sort:'-created'}, function(data) {
+    TopicFactory.query({limit:Vipra.config.latestItems, sort:'-created'}, function(data) {
       $scope.latestTopics = data;
     });
 
-    WordFactory.query({limit:Vipra.const.latestItems, sort:'-created'}, function(data) {
+    WordFactory.query({limit:Vipra.config.latestItems, sort:'-created'}, function(data) {
       $scope.latestWords = data;
     });
 
@@ -37,7 +37,7 @@
       if($scope.search) {
         $location.search('query', $scope.search);
         $scope.searching = true;
-        SearchFactory.query({limit:Vipra.const.searchResults, query:$scope.search}, function(data) {
+        SearchFactory.query({limit:Vipra.config.searchResults, query:$scope.search}, function(data) {
           $scope.searching = false;
           $scope.searchResults = data;
         });
@@ -266,11 +266,7 @@
     function($scope, $state, $location, ArticleFactory, Store) {
 
     $scope.page = Math.max($location.search().page || 1, 1);
-    $scope.limit = Vipra.const.pageSize;
-
-    $scope.changePage = function(page) {
-      $scope.page = page;
-    };
+    $scope.limit = Vipra.config.pageSize;
 
     $scope.$watchGroup(['page','sort','order'], function() {
       ArticleFactory.query({
@@ -348,14 +344,10 @@
     function($scope, $location, Store, TopicFactory) {
 
     $scope.page = Math.max($location.search().page || 1, 1);
-    $scope.limit = Vipra.const.pageSize;
+    $scope.limit = Vipra.config.pageSize;
     $scope.sort = Store('sorttopics') || 'name';
     $scope.order = Store('ordertopics') || '+';
 
-    $scope.changePage = function(page) {
-      $scope.page = page;
-    };
-
     $scope.$watchGroup(['page','sort','order'], function() {
       TopicFactory.query({
         skip: ($scope.page-1)*$scope.limit,
@@ -423,14 +415,10 @@
     function($scope, $stateParams, $location, Store, TopicFactory) {
 
     $scope.page = Math.max($location.search().page || 1, 1);
-    $scope.limit = Vipra.const.pageSize;
+    $scope.limit = Vipra.config.pageSize;
     $scope.sort = Store('sortarticles') || 'title';
     $scope.order = Store('orderarticles') || '+';
 
-    $scope.changePage = function(page) {
-      $scope.page = page;
-    };
-
     $scope.$watchGroup(['page','sort','order'], function() {
       TopicFactory.articles({
         id: $stateParams.id,
@@ -446,6 +434,16 @@
 
   }]);
 
+  /**
+   * Topic Show Similar route
+   */
+  app.controller('TopicsSimilarController', ['$scope', '$stateParams', 'TopicFactory',
+    function($scope, $stateParams,  TopicFactory) {
+
+    $scope.typeLabel = $stateParams.type.substring(3);
+
+  }]);
+
   /****************************************************************************
    * Word Controllers
    ****************************************************************************/
@@ -461,10 +459,6 @@
     $scope.sort = Store('sortwords') || 'id';
     $scope.order = Store('orderwords') || '+';
 
-    $scope.changePage = function(page) {
-      $scope.page = page;
-    };
-
     $scope.$watchGroup(['page','sort','order'], function() {
       WordFactory.query({
         skip: ($scope.page-1)*$scope.limit,
@@ -531,11 +525,8 @@
 
       $scope.calculatePages();
 
-      var change = $scope.change() || function() {};
-
       $scope.changePage = function(page) {
         $scope.page = page;
-        change(page);
         window.scrollTo(0,0);
       };
 
diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js
index 469d9c48f55fa5c8b75dca076836cd5b3d551307..6493dbce115f60b8a6bb7002e431a27d4adabf02 100644
--- a/vipra-ui/app/js/directives.js
+++ b/vipra-ui/app/js/directives.js
@@ -135,14 +135,17 @@
 
   app.directive('bsDropdown', function() {
     return {
+      scope: {
+        label: '@',
+        align: '@'
+      },
       templateUrl: 'html/directives/dropdown.html',
       transclude: true,
       replace: true,
-      link: function($scope, $elem, $attrs) {
+      link: function($scope) {
         $scope.dropdownId = Vipra.randomId();
-        $scope.label = $attrs.label;
         $scope.align = 'dropdown-menu-left';
-        if($attrs.align === 'right')
+        if($scope.align === 'right')
           $scope.align = 'dropdown-menu-right';
       }
     };
diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less
index da98522ab4bc21f5643e493bded0c5ec7b589b85..e48007ed6ae8b5d351f37b99c54d835452d7df8c 100644
--- a/vipra-ui/app/less/app.less
+++ b/vipra-ui/app/less/app.less
@@ -126,7 +126,7 @@ ul.dashed {
 
 .graph-legend {
   position: absolute;
-  top: 60px;
+  top: 97px;
   left: 10px;
   font-weight: bold;
   padding: 10px;
@@ -174,7 +174,7 @@ ul.dashed {
 }
 
 .navpadding {
-  padding-top: 50px;
+  padding-top: 87px;
 }
 
 .form-control-inline {
@@ -245,6 +245,10 @@ ul.dashed {
   margin-top: -2px;
 }
 
+.navbar-breadcrumbs {
+  margin-bottom: 0;
+}
+
 revolve-select, [revolve-select] {
   .noselect;
   cursor: pointer;
diff --git a/vipra-ui/bower.json b/vipra-ui/bower.json
index 02a607dfe4a39d53e7e20146dd11c1846c966cba..d1495f65d49fb7395fcbf9fad62dcccbbcf10930 100644
--- a/vipra-ui/bower.json
+++ b/vipra-ui/bower.json
@@ -28,6 +28,7 @@
     "angular-websocket": "^1.0.14",
     "moment": "^2.11.2",
     "nya-bootstrap-select": "^2.1.3",
-    "nprogress": "^0.2.0"
+    "nprogress": "^0.2.0",
+    "angular-breadcrumb": "^0.4.1"
   }
 }
diff --git a/vipra-ui/build.sh b/vipra-ui/build.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ee2f1c881fd2a425cb23141b67e71594b77bcc08
--- /dev/null
+++ b/vipra-ui/build.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+rm -rf public
+
+npm install
+bower install
+gulp build
+
+exit 0
diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js
index 4492076aeb769beeb4c7d40d78af24d173687b70..3a7fd6005849079257082c03082524e389ae61bf 100644
--- a/vipra-ui/gulpfile.js
+++ b/vipra-ui/gulpfile.js
@@ -15,6 +15,7 @@ var assets = {
     'bower_components/angular-sanitize/angular-sanitize.min.js',
     'bower_components/angular-ui-router/release/angular-ui-router.min.js',
     'bower_components/angular-websocket/angular-websocket.min.js',
+    'bower_components/angular-breadcrumb/dist/angular-breadcrumb.min.js',
     'bower_components/bootstrap/dist/js/bootstrap.min.js',
     'bower_components/highcharts/highcharts.js',
     'bower_components/vis/dist/vis.min.js',
diff --git a/vipra-ui/package.json b/vipra-ui/package.json
index 85743ec707d7cd144babcb70fa153651f9443f49..83d14ec7469960bd0bd8025638c23662432dad1e 100644
--- a/vipra-ui/package.json
+++ b/vipra-ui/package.json
@@ -5,7 +5,8 @@
   "author": "Eike Cochu",
   "private": true,
   "devDependencies": {
-    "gulp": "^3.9.0",
+    "bower": "^1.7.7",
+    "gulp": "^3.9.1",
     "gulp-angular-templatecache": "^1.8.0",
     "gulp-concat": "^2.6.0",
     "gulp-cssnano": "^2.1.0",
diff --git a/vipra-util/src/main/java/de/vipra/util/ConvertStream.java b/vipra-util/src/main/java/de/vipra/util/ConvertStream.java
deleted file mode 100644
index f7d926ffb0cf7f499971c6630f873f49c8dd84ac..0000000000000000000000000000000000000000
--- a/vipra-util/src/main/java/de/vipra/util/ConvertStream.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package de.vipra.util;
-
-import java.io.BufferedReader;
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Queue;
-
-import de.vipra.util.ex.NotImplementedException;
-
-/**
- * The convert stream class is used to create a converting stream of objects
- * from a file resource. The file is read sequentially and objects are
- * deserialized from the file contents, according to some convert method of the
- * convert stream. The amount of lines read by the stream is decided by the
- * convert method.
- *
- * @param <T>
- *            object type returned by the stream
- */
-public abstract class ConvertStream<T> implements Closeable, AutoCloseable, Iterator<T>, Iterable<T> {
-
-	private final BufferedReader reader;
-	private Queue<String> buffer;
-
-	public ConvertStream(File file) throws FileNotFoundException {
-		this.reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
-		this.buffer = new LinkedList<>();
-	}
-
-	@Override
-	public void close() throws IOException {
-		reader.close();
-	}
-
-	/**
-	 * Returns the next file line.
-	 * 
-	 * @return next file line, if available
-	 */
-	protected String nextLine() {
-		if (buffer.isEmpty()) {
-			try {
-				return reader.readLine();
-			} catch (IOException e) {}
-		}
-		return buffer.poll();
-	}
-
-	/**
-	 * Push back line into a line buffer, if not processed
-	 * 
-	 * @param line
-	 *            line to buffer
-	 */
-	protected void buffer(String line) {
-		buffer.offer(line);
-	}
-
-	/**
-	 * Returns true if next line available. Reads and buffers the next line of
-	 * the selected file.
-	 */
-	@Override
-	public boolean hasNext() {
-		String line = null;
-		try {
-			line = reader.readLine();
-		} catch (IOException e) {}
-		if (line != null) {
-			buffer.offer(line);
-			return true;
-		}
-		return false;
-	}
-
-	/**
-	 * Returns the next object in the stream, converted by the convert method
-	 * 
-	 * @return converted object
-	 */
-	@Override
-	public T next() {
-		if (buffer.isEmpty()) {
-			try {
-				return convert(reader.readLine());
-			} catch (IOException e) {}
-		}
-		return convert(buffer.poll());
-	}
-
-	@Override
-	public void remove() {
-		throw new NotImplementedException();
-	}
-
-	@Override
-	public Iterator<T> iterator() {
-		return this;
-	}
-
-	/**
-	 * Convert method. This method is used to deserialize a file line into an
-	 * object of type T. If more lines are required for deserialization, they
-	 * can be requested by using next().
-	 * 
-	 * @param line
-	 *            the line to be converted
-	 * @return the converted object
-	 */
-	public abstract T convert(String line);
-
-}
diff --git a/vipra-util/src/main/java/de/vipra/util/CountMap.java b/vipra-util/src/main/java/de/vipra/util/CountMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..45b3d48d1769a83276a0e126b71ab3502bd465b4
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/CountMap.java
@@ -0,0 +1,32 @@
+package de.vipra.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class CountMap<T> {
+
+	private final Map<T, Integer> map;
+
+	public CountMap() {
+		this.map = new HashMap<>();
+	}
+
+	public CountMap(Map<T, Integer> map) {
+		this.map = map;
+	}
+
+	public void count(T t) {
+		Integer count = map.get(t);
+		if (count == null)
+			map.put(t, 1);
+		else
+			map.put(t, count + 1);
+	}
+
+	public Set<Entry<T, Integer>> entrySet() {
+		return map.entrySet();
+	}
+
+}
diff --git a/vipra-util/src/main/java/de/vipra/util/FrequencyList.java b/vipra-util/src/main/java/de/vipra/util/FrequencyList.java
deleted file mode 100644
index f512248d66c7d35192eb9e7f668749ae6d8b1632..0000000000000000000000000000000000000000
--- a/vipra-util/src/main/java/de/vipra/util/FrequencyList.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package de.vipra.util;
-
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-public class FrequencyList<T> implements Iterable<T> {
-
-	private Map<T, Integer> map = new LinkedHashMap<>();
-
-	public void add(T t) {
-		if (map.containsKey(t))
-			map.put(t, map.get(t) + 1);
-		else
-			map.put(t, 1);
-	}
-
-	public Integer get(T t) {
-		return map.get(t);
-	}
-
-	@Override
-	public Iterator<T> iterator() {
-		return map.keySet().iterator();
-	}
-
-}
diff --git a/vipra-util/src/main/java/de/vipra/util/Pair.java b/vipra-util/src/main/java/de/vipra/util/Pair.java
deleted file mode 100644
index 23c2e378094845c731aa2a7bbf90b1caa50ac8eb..0000000000000000000000000000000000000000
--- a/vipra-util/src/main/java/de/vipra/util/Pair.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package de.vipra.util;
-
-public class Pair<X, Y> {
-
-	private X x;
-	private Y y;
-
-	public Pair() {}
-
-	public Pair(X x, Y y) {
-		this.x = x;
-		this.y = y;
-	}
-
-	public X x() {
-		return x;
-	}
-
-	public void setX(X x) {
-		this.x = x;
-	}
-
-	public Y y() {
-		return y;
-	}
-
-	public void setY(Y y) {
-		this.y = y;
-	}
-
-	public static <X, Y> Pair<X, Y> pair(X x, Y y) {
-		return new Pair<>(x, y);
-	}
-
-}
diff --git a/vipra-util/src/main/java/de/vipra/util/Tuple.java b/vipra-util/src/main/java/de/vipra/util/Tuple.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb7995d3b592f1d0e7715d5dd50d1a29dfddcfc5
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/Tuple.java
@@ -0,0 +1,35 @@
+package de.vipra.util;
+
+public class Tuple<X, Y> {
+
+	private X first;
+	private Y second;
+
+	public Tuple() {}
+
+	public Tuple(X first, Y second) {
+		this.first = first;
+		this.second = second;
+	}
+
+	public X first() {
+		return first;
+	}
+
+	public void setFirst(X first) {
+		this.first = first;
+	}
+
+	public Y second() {
+		return second;
+	}
+
+	public void setSecond(Y second) {
+		this.second = second;
+	}
+
+	public static <X, Y> Tuple<X, Y> pair(X first, Y second) {
+		return new Tuple<>(first, second);
+	}
+
+}
diff --git a/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java b/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java
index b3c6b025faeb103cd2937ca8d73869b53d003d95..ec0e92cf76332c85b4d344942645a2aaef0e05d5 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java
@@ -36,7 +36,7 @@ public class ArticleFull extends FileModel<ObjectId> implements Serializable {
 	public static final Logger log = LoggerFactory.getLogger(ArticleFull.class);
 
 	@Id
-	private ObjectId id;
+	private ObjectId id = new ObjectId();
 
 	@ElasticIndex("title")
 	private String title;
diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java
index 6dd42ea1657e720875c51a2cb8591f45057e6a20..c46f5a25597b65cfd09bdd7ee02f004846c5f718 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java
@@ -22,8 +22,10 @@ import de.vipra.util.an.QueryIgnore;
 public class TopicFull implements Model<ObjectId>, Serializable {
 
 	@Id
-	private ObjectId id;
+	private ObjectId id = new ObjectId();
+
 	private String name;
+
 	private Integer index;
 
 	@Embedded
@@ -34,6 +36,7 @@ public class TopicFull implements Model<ObjectId>, Serializable {
 	private List<ArticleFull> articles;
 
 	private Date created;
+
 	private Date modified;
 
 	@Override
diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicRef.java b/vipra-util/src/main/java/de/vipra/util/model/TopicRef.java
index c5de02e6b750df507e7d8d0f9b15bc2eed307f21..00837669c2ddee92726a0e63f0f557b418206754 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/TopicRef.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/TopicRef.java
@@ -4,26 +4,15 @@ import java.io.Serializable;
 
 import org.mongodb.morphia.annotations.Embedded;
 import org.mongodb.morphia.annotations.Reference;
-import org.mongodb.morphia.annotations.Transient;
 
 @SuppressWarnings("serial")
 @Embedded
 public class TopicRef implements Comparable<TopicRef>, Serializable {
 
-	@Transient
-	private String topicIndex;
 	@Reference(ignoreMissing = true)
 	private Topic topic;
 	private Integer count;
 
-	public String getTopicIndex() {
-		return topicIndex;
-	}
-
-	public void setTopicIndex(String index) {
-		this.topicIndex = index;
-	}
-
 	public Integer getCount() {
 		return count;
 	}
@@ -47,8 +36,7 @@ public class TopicRef implements Comparable<TopicRef>, Serializable {
 
 	@Override
 	public String toString() {
-		return TopicRef.class.getSimpleName() + "[topicIndex:" + topicIndex + ", topic: " + topic + ", count:" + count
-				+ "]";
+		return TopicRef.class.getSimpleName() + "[topic: " + topic + ", count:" + count + "]";
 	}
 
 }
diff --git a/vipra-util/src/main/java/de/vipra/util/service/MongoService.java b/vipra-util/src/main/java/de/vipra/util/service/MongoService.java
index 2f4a4bc485c40fc76f7d819f78ee46aaecb7aae5..c975d865029b23dcf1eced9520887bed77c5f0d9 100644
--- a/vipra-util/src/main/java/de/vipra/util/service/MongoService.java
+++ b/vipra-util/src/main/java/de/vipra/util/service/MongoService.java
@@ -13,7 +13,7 @@ import org.mongodb.morphia.query.UpdateOperations;
 import de.vipra.util.Config;
 import de.vipra.util.ListUtils;
 import de.vipra.util.Mongo;
-import de.vipra.util.Pair;
+import de.vipra.util.Tuple;
 import de.vipra.util.an.QueryIgnore;
 import de.vipra.util.an.UpdateIgnore;
 import de.vipra.util.ex.ConfigException;
@@ -75,7 +75,7 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service
 	}
 
 	@Override
-	public List<Type> getMultiple(Integer skip, Integer limit, String sortBy, Pair<String, Object> criteria,
+	public List<Type> getMultiple(Integer skip, Integer limit, String sortBy, Tuple<String, Object> criteria,
 			String... fields) {
 		return getMultiple(
 				QueryBuilder.builder().skip(skip).limit(limit).sortBy(sortBy).fields(true, fields).criteria(criteria));
@@ -93,8 +93,8 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service
 			if (builder.getSortBy() != null)
 				query.order(builder.getSortBy());
 			if (builder.getCriteria() != null)
-				for (Pair<String, Object> criteria : builder.getCriteria())
-					query.field(criteria.x()).equal(criteria.y());
+				for (Tuple<String, Object> criteria : builder.getCriteria())
+					query.field(criteria.first()).equal(criteria.second());
 			if (builder.getFields() != null) {
 				String[] fields = builder.getFields();
 				if (builder.isInclude()) {
@@ -219,8 +219,8 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service
 		if (builder.getLimit() != null && builder.getLimit() > 0)
 			query.limit(builder.getLimit());
 		if (builder.getCriteria() != null)
-			for (Pair<String, Object> criteria : builder.getCriteria())
-				query.field(criteria.x()).equal(criteria.y());
+			for (Tuple<String, Object> criteria : builder.getCriteria())
+				query.field(criteria.first()).equal(criteria.second());
 
 		return datastore.getCount(query);
 	}
diff --git a/vipra-util/src/main/java/de/vipra/util/service/Service.java b/vipra-util/src/main/java/de/vipra/util/service/Service.java
index 3e9aff75b8cd10cf9d73e01c854463d3c1c23cd7..136e57580ddad941af8fb1632c29c4818192ad69 100644
--- a/vipra-util/src/main/java/de/vipra/util/service/Service.java
+++ b/vipra-util/src/main/java/de/vipra/util/service/Service.java
@@ -3,7 +3,7 @@ package de.vipra.util.service;
 import java.util.ArrayList;
 import java.util.List;
 
-import de.vipra.util.Pair;
+import de.vipra.util.Tuple;
 import de.vipra.util.model.Model;
 
 /**
@@ -39,7 +39,7 @@ public interface Service<Type extends Model<IdType>, IdType, E extends Exception
 	/**
 	 * @see {@link Service#getMultiple(QueryBuilder)}
 	 */
-	List<Type> getMultiple(Integer skip, Integer limit, String sortBy, Pair<String, Object> criteria, String... fields)
+	List<Type> getMultiple(Integer skip, Integer limit, String sortBy, Tuple<String, Object> criteria, String... fields)
 			throws E;
 
 	/**
@@ -158,7 +158,7 @@ public interface Service<Type extends Model<IdType>, IdType, E extends Exception
 		private Integer skip;
 		private Integer limit;
 		private String sortBy;
-		private List<Pair<String, Object>> criteria;
+		private List<Tuple<String, Object>> criteria;
 		private String[] fields;
 		private boolean include;
 
@@ -218,7 +218,7 @@ public interface Service<Type extends Model<IdType>, IdType, E extends Exception
 		 */
 		public QueryBuilder criteria(String field, Object value) {
 			if (field != null && value != null && !field.isEmpty())
-				criteria(Pair.pair(field, value));
+				criteria(Tuple.pair(field, value));
 			return this;
 		}
 
@@ -229,7 +229,7 @@ public interface Service<Type extends Model<IdType>, IdType, E extends Exception
 		 *            field value pair to compare
 		 * @return QueryBuilder instance
 		 */
-		public QueryBuilder criteria(Pair<String, Object> pair) {
+		public QueryBuilder criteria(Tuple<String, Object> pair) {
 			if (pair != null) {
 				if (criteria == null) {
 					criteria = new ArrayList<>();
@@ -269,7 +269,7 @@ public interface Service<Type extends Model<IdType>, IdType, E extends Exception
 			return sortBy.startsWith("+") ? sortBy.substring(1) : sortBy;
 		}
 
-		public List<Pair<String, Object>> getCriteria() {
+		public List<Tuple<String, Object>> getCriteria() {
 			return criteria;
 		}