diff --git a/annotation/src/main/java/lcsb/mapviewer/annotation/services/ModelAnnotator.java b/annotation/src/main/java/lcsb/mapviewer/annotation/services/ModelAnnotator.java
index 60ece883257940ee525c2e60f4dd2e6038a97c9f..d4bfb99a236e3ec903624b3e3f13588214dd341a 100644
--- a/annotation/src/main/java/lcsb/mapviewer/annotation/services/ModelAnnotator.java
+++ b/annotation/src/main/java/lcsb/mapviewer/annotation/services/ModelAnnotator.java
@@ -25,6 +25,7 @@ import lcsb.mapviewer.annotation.services.annotators.EnsemblAnnotator;
 import lcsb.mapviewer.annotation.services.annotators.EntrezAnnotator;
 import lcsb.mapviewer.annotation.services.annotators.GoAnnotator;
 import lcsb.mapviewer.annotation.services.annotators.HgncAnnotator;
+import lcsb.mapviewer.annotation.services.annotators.KeggAnnotator;
 import lcsb.mapviewer.annotation.services.annotators.PdbAnnotator;
 import lcsb.mapviewer.annotation.services.annotators.ReconAnnotator;
 import lcsb.mapviewer.annotation.services.annotators.StitchAnnotator;
@@ -129,6 +130,12 @@ public class ModelAnnotator {
 	 */
 	@Autowired
 	private HgncAnnotator					 hgncAnnotator;
+	
+	/**
+	 * Service accessing <a href= "http://www.kegg.jp/" > KEGG EC Nomenclature</a>.
+	 */
+	@Autowired
+	private KeggAnnotator					 keggAnnotator;
 
 	/**
 	 * Service accessing <a href= "http://www.ncbi.nlm.nih.gov/gene/" >Entrez</a>.
@@ -187,6 +194,7 @@ public class ModelAnnotator {
 		addAnnotator(uniprotAnnotator);
 		addAnnotator(goAnnotator);
 		addAnnotator(hgncAnnotator);
+		addAnnotator(keggAnnotator);
 		addAnnotator(pdbAnnotator);
 		addAnnotator(reconAnnotator);
 		addAnnotator(entrezAnnotator);
diff --git a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/BrendaAnnotator.java b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/BrendaAnnotator.java
index cf4feb8ac6c20d943dbeade01661b2443f57efa7..75bc68e62ffb04467504ecf8851bf199f7290ac1 100644
--- a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/BrendaAnnotator.java
+++ b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/BrendaAnnotator.java
@@ -47,7 +47,7 @@ public class BrendaAnnotator extends ElementAnnotator implements IExternalServic
 	private TairAnnotator		tairAnnotator;	
 
 	/**
-	 * Pattern used for finding UniProt symbol from TAIR info page .
+	 * Pattern used for finding EC symbol from UniProt page .
 	 */
 	private Pattern				uniprotECMatcher	= Pattern.compile("EC=((\\d+\\.-\\.-\\.-)|(\\d+\\.\\d+\\.-\\.-)|(\\d+\\.\\d+\\.\\d+\\.-)|(\\d+\\.\\d+\\.\\d+\\.\\d+))");
 
@@ -66,7 +66,7 @@ public class BrendaAnnotator extends ElementAnnotator implements IExternalServic
 		this.setCache(null);
 
 		try {
-			Collection<MiriamData> mds = uniprotToBrenda(createMiriamData(MiriamType.UNIPROT, "P12345"));
+			Collection<MiriamData> mds = new UniprotAnnotator().uniProtToEC(createMiriamData(MiriamType.UNIPROT, "P12345"));
 
 			status.setStatus(ExternalServiceStatusType.OK);
 			List<String> ecs = new ArrayList<>();
@@ -99,6 +99,7 @@ public class BrendaAnnotator extends ElementAnnotator implements IExternalServic
 					mdTair = md;
 				}				
 			}			
+			
 			if (mdTair != null) {
 				tairAnnotator.annotateElement(object);
 			}
@@ -110,48 +111,30 @@ public class BrendaAnnotator extends ElementAnnotator implements IExternalServic
 				}			
 			}
 			
+			UniprotAnnotator uniprotAnnotator = new UniprotAnnotator();
+			
+			
 			List<String> ecIds = new ArrayList<String>();
 			for (MiriamData mdUniprot: mdUniprots) {
-				Collection<MiriamData> mdBrendas = uniprotToBrenda(mdUniprot);
-				if (mdBrendas != null) {
-					for (MiriamData mdBrenda: mdBrendas) {
-						if (ecIds.indexOf(mdBrenda.getResource()) == -1) {
-							ecIds.add(mdBrenda.getResource());
-							object.addMiriamData(mdBrenda);						
+				try {
+					Collection<MiriamData> mdECs =  uniprotAnnotator.uniProtToEC(mdUniprot);
+					if (mdECs != null) {
+						for (MiriamData mdEC: mdECs) {
+							mdEC.setAnnotator(BrendaAnnotator.class);
+							mdEC.setDataType(MiriamType.BRENDA);
+							if (ecIds.indexOf(mdEC.getResource()) == -1) {
+								ecIds.add(mdEC.getResource());
+								object.addMiriamData(mdEC);						
+							}					
 						}					
-					}					
-				}								
-			}
+					}
+				} catch (UniprotSearchException e) {
+					logger.warn("Cannot find EC data for UniProt id: " + mdUniprot.getResource());
+				}												
+			}			
 		}		
 	}
-
-	/**
-	 * Returns URL to UniProt restfull API about UniProt entry.
-	 * 
-	 * @param uniprotId
-	 *          UniProt identifier
-	 * @return URL to UniProt restfull API about UniProt entry
-	 */
-	private String getUniprotUrl(String uniprotId) {
-		return "http://www.uniprot.org/uniprot/" + uniprotId + ".txt";
-	}
-
-	/**
-	 * Parse UniProt webpage to find information about
-	 * {@link MiriamType#BRENDA}, i.e. EC, and returns them.
-	 * 
-	 * @param pageContent
-	 *          UniProt info page
-	 * @return BRENDA family identifier, i.e. EC, found on the page
-	 */
-	private Collection<MiriamData> parseUniprot(String pageContent) {
-		Collection<MiriamData> result = new HashSet<MiriamData>();
-		Matcher m = uniprotECMatcher.matcher(pageContent);
-		while (m.find()) {
-			result.add(createMiriamData(MiriamType.BRENDA, m.group(1)));
-		}
-		return result;
-	}
+	
 
 	@Override
 	public Object refreshCacheQuery(Object query) throws SourceNotAvailable {
@@ -174,41 +157,8 @@ public class BrendaAnnotator extends ElementAnnotator implements IExternalServic
 		return result;
 	}
 
-	/**
-	 * Transform UniProt identifier to CAZy identifier.
-	 * 
-	 * @param UniProt
-	 *          {@link MiriamData} with UniProt identifier
-	 * @return Collection of {@link MiriamData} with BRENDA identifier
-	 * @throws AnnotatorException
-	 *           thrown when there is a problem with accessing external database
-	 */
-	public Collection<MiriamData> uniprotToBrenda(MiriamData uniprot) throws AnnotatorException {
-		if (uniprot == null) {
-			return null;
-		}
-
-		if (!MiriamType.UNIPROT.equals(uniprot.getDataType())) {
-			throw new InvalidArgumentException(MiriamType.UNIPROT + " expected.");
-		}
 
-		String accessUrl = getUniprotUrl(uniprot.getResource());
-		try {
-			String pageContent = getWebPageContent(accessUrl);
-			Collection<MiriamData> collection = parseUniprot(pageContent);
-			if (collection.size() > 0) {
-				return collection;
-			} else {
-				logger.warn("Cannot find EC data for UniProt id: " + uniprot.getResource());
-				return null;
-			}
-		} catch (WrongResponseCodeIOException exception) {
-			logger.warn("Wrong response code when retrieving EC data for UniProt id: " + uniprot.getResource());
-			return null;
-		} catch (IOException exception) {
-			throw new AnnotatorException(exception);
-		}
-	}
+	
 
 	@Override
 	public String getCommonName() {
diff --git a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/KeggAnnotator.java b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/KeggAnnotator.java
new file mode 100644
index 0000000000000000000000000000000000000000..99a67fdf12b230aa5ed10716ce3be62c63ac8602
--- /dev/null
+++ b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/KeggAnnotator.java
@@ -0,0 +1,248 @@
+package lcsb.mapviewer.annotation.services.annotators;
+
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import lcsb.mapviewer.annotation.cache.GeneralCacheInterface;
+import lcsb.mapviewer.annotation.cache.SourceNotAvailable;
+import lcsb.mapviewer.annotation.cache.WebPageDownloader;
+import lcsb.mapviewer.annotation.services.ExternalServiceStatus;
+import lcsb.mapviewer.annotation.services.ExternalServiceStatusType;
+import lcsb.mapviewer.annotation.services.IExternalService;
+import lcsb.mapviewer.annotation.services.WrongResponseCodeIOException;
+import lcsb.mapviewer.common.exception.InvalidArgumentException;
+import lcsb.mapviewer.model.map.BioEntity;
+import lcsb.mapviewer.model.map.MiriamData;
+import lcsb.mapviewer.model.map.MiriamType;
+import lcsb.mapviewer.model.map.species.Gene;
+import lcsb.mapviewer.model.map.species.GenericProtein;
+import lcsb.mapviewer.model.map.species.Protein;
+import lcsb.mapviewer.model.map.species.Rna;
+import lcsb.mapviewer.model.map.species.Species;
+
+/**
+ * This is a class that implements KEGG annotator which extract from KEGG 
+ * PUBMED records and homologous information about homologous genes in 
+ * different organisms based on parameterization of the annotator.
+ * 
+ * @author David Hoksza
+ * 
+ */
+public class KeggAnnotator extends ElementAnnotator implements IExternalService {
+
+	/**
+	 * Default class logger.
+	 */
+	private static Logger	logger					= Logger.getLogger(KeggAnnotator.class);
+	
+	/**
+	 * Pattern used for finding PUBMED IDs in KEGG page.
+	 */
+	private Pattern				pubmedMatcher	= Pattern.compile("\\[PMID:(\\d+)\\]");
+
+	
+	/**
+	 * Service used for annotation of entities using {@link MiriamType#TAIR_LOCUS TAIR}.
+	 */
+	@Autowired
+	private TairAnnotator		tairAnnotator;
+	
+	/**
+	 * Service used for retrieving EC numbers based on {@link MiriamType#UNIPROT}
+	 */
+	@Autowired
+	private UniprotAnnotator		uniprotAnnotator;
+
+	/**
+	 * Default constructor.
+	 */
+	public KeggAnnotator() {
+		super(KeggAnnotator.class, new Class[] { Protein.class, Gene.class, Rna.class }, false);
+	}
+
+	@Override
+	public ExternalServiceStatus getServiceStatus() {
+		ExternalServiceStatus status = new ExternalServiceStatus(getCommonName(), getUrl());
+
+		GeneralCacheInterface cacheCopy = getCache();
+		this.setCache(null);
+
+		try {
+			Species protein = new GenericProtein("id");
+			MiriamData mdEC = createMiriamData(MiriamType.EC, "3.1.2.14");
+			mdEC.setAnnotator(null);
+			protein.addMiriamData(mdEC);
+			annotateElement(protein);
+
+			status.setStatus(ExternalServiceStatusType.OK);
+			
+			Set<String> pmids = new HashSet<String>();
+			pmids.add("30409");
+			pmids.add("3134");
+			int cntMatches = 0;
+			for (MiriamData md: protein.getMiriamData()) {
+				if (pmids.contains(md.getResource())) {
+					cntMatches++;
+				}				
+			}
+			
+			if (cntMatches != 2) {
+				status.setStatus(ExternalServiceStatusType.CHANGED);
+			}
+		} catch (Exception e) {
+			logger.error(status.getName() + " is down", e);
+			status.setStatus(ExternalServiceStatusType.DOWN);
+		}
+		this.setCache(cacheCopy);
+		return status;
+	}
+	
+	//private Boolean resourceInCollection(String resource, Collection<MiriamData> list) {
+	//	
+	//	for (MiriamData md: list) {
+	//		if (md.getResource() == resource) {
+	//			return true;
+	//		}
+	//		
+	//	}
+	//	return false;	
+	//}
+	
+	@Override
+	public void annotateElement(BioEntity object) throws AnnotatorException {
+		if (isAnnotatable(object)) {
+			
+			MiriamData mdTair = null;
+			for (MiriamData md : object.getMiriamData()) {
+				Class<?> annotator =  md.getAnnotator();
+				if (annotator == this.getClass()) {
+					//this annotator was already used
+					return;
+				}
+				else if (md.getDataType().equals(MiriamType.TAIR_LOCUS) && 
+				 		(annotator == null ) ) {
+				 	mdTair = md;
+				}				
+			}
+			
+			if (mdTair != null) tairAnnotator.annotateElement(object);
+			
+			MiriamData mdUniprot = null;
+			for (MiriamData md : object.getMiriamData()) {
+				if (md.getDataType().equals(MiriamType.UNIPROT) ) {
+				 	mdUniprot = md;
+				}
+			}
+			if (mdUniprot != null) uniprotAnnotator.annotateElement(object);
+			
+			
+			
+			Set<String> ecs = new HashSet<String>();						
+			for (MiriamData md : object.getMiriamData()) {
+				if (md.getDataType().equals(MiriamType.EC)) {					
+					ecs.add(md.getResource());
+				}
+			}
+			
+			if (ecs.size() == 0) {				 
+				return;
+			}			
+									
+			//annotate from KEGG
+			Set<MiriamData> annotations = new HashSet<MiriamData>();
+			for (String ec: ecs) {
+				
+				String accessUrl = getKeggUrl(ec);
+				
+				try {
+					String pageContent = getWebPageContent(accessUrl);
+					annotations.addAll(parseKegg(pageContent));
+						
+				} catch (WrongResponseCodeIOException exception) {
+					logger.warn("Cannot find kegg data for id: " + ec);
+				} catch (IOException exception) {
+					throw new AnnotatorException(exception);
+				}
+			}
+			object.addMiriamData(annotations);
+		}		
+	}
+	
+	/**
+	 * Returns url to KEGG restfull API about enzyme classification.
+	 * 
+	 * @param ecId
+	 *          enzyme classification
+	 * @return url to KEGG restfull API about given EC
+	 */
+	private String getKeggUrl(String ecId) {
+		return "http://rest.kegg.jp/get/" + ecId;
+	}
+	
+	/**
+	 * Parse KEGG webpage to find information about
+	 * {@link MiriamType#PUBMED}s and returns them.
+	 * 
+	 * @param pageContent
+	 *          Kegg page
+	 * @return {@link MiriamType#PUBMED}s found on the page
+	 */
+	private Collection<MiriamData> parseKegg(String pageContent) {
+		Collection<MiriamData> result = new HashSet<MiriamData>();
+		Matcher m = pubmedMatcher.matcher(pageContent);
+		while (m.find()) {
+			result.add(createMiriamData(MiriamType.PUBMED, m.group(1)));
+		}
+		return result;
+	}
+
+	@Override
+	public Object refreshCacheQuery(Object query) throws SourceNotAvailable {
+		String name;
+		String result = null;
+		if (query instanceof String) {
+			name = (String) query;
+			if (name.startsWith("http")) {
+				try {
+					result = getWebPageContent(name);
+				} catch (IOException e) {
+					throw new SourceNotAvailable(e);
+				}
+			} else {
+				throw new InvalidArgumentException("Don't know what to do with query: " + query);
+			}
+		} else {
+			throw new InvalidArgumentException("Don't know what to do with class: " + query.getClass());
+		}
+		return result;
+	}
+	
+
+	@Override
+	public String getCommonName() {
+		return "KEGG";
+	}
+
+	@Override
+	public String getUrl() {
+		return "http://www.genome.jp/kegg/";
+	}
+
+	@Override
+	protected WebPageDownloader getWebPageDownloader() {
+		return super.getWebPageDownloader();
+	}
+
+	@Override
+	protected void setWebPageDownloader(WebPageDownloader webPageDownloader) {
+		super.setWebPageDownloader(webPageDownloader);
+	}
+}
diff --git a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/UniprotAnnotator.java b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/UniprotAnnotator.java
index ca9c48cca44dc7d349ee22b5a7a29a1a15957227..8f940055079b6a4b74e7aed4c8ba53cc376e192b 100644
--- a/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/UniprotAnnotator.java
+++ b/annotation/src/main/java/lcsb/mapviewer/annotation/services/annotators/UniprotAnnotator.java
@@ -47,6 +47,12 @@ public class UniprotAnnotator extends ElementAnnotator implements IExternalServi
 	 * Pattern used for finding entrez identifier from uniprot info page .
 	 */
 	private Pattern				uniprotToEntrez	= Pattern.compile("DR[\\ ]+GeneID;\\ ([^;\\ ]+)");
+	
+	/**
+	 * Pattern used for finding EC symbol from UniProt info page .
+	 */
+	private Pattern				uniprotToEC	= Pattern.compile("EC=((\\d+\\.-\\.-\\.-)|(\\d+\\.\\d+\\.-\\.-)|(\\d+\\.\\d+\\.\\d+\\.-)|(\\d+\\.\\d+\\.\\d+\\.\\d+))");
+
 
 	/**
 	 * Class used for some simple operations on {@link BioEntity} elements.
@@ -108,6 +114,7 @@ public class UniprotAnnotator extends ElementAnnotator implements IExternalServi
 					Set<MiriamData> annotations = new HashSet<MiriamData>();
 					annotations.addAll(parseHgnc(pageContent));
 					annotations.addAll(parseEntrez(pageContent));
+					annotations.addAll(parseEC(pageContent));
 					if (!uniprotFound) {
 						annotations.add(createMiriamData(MiriamType.UNIPROT, uniprotId));
 					}
@@ -170,6 +177,23 @@ public class UniprotAnnotator extends ElementAnnotator implements IExternalServi
 		}
 		return result;
 	}
+	
+	/**
+	 * Parse UniProt webpage to find information about
+	 * {@link MiriamType#EC}s and returns them.
+	 * 
+	 * @param pageContent
+	 *          UniProt info page
+	 * @return EC found on the page
+	 */
+	private Collection<MiriamData> parseEC(String pageContent) {
+		Collection<MiriamData> result = new HashSet<MiriamData>();
+		Matcher m = uniprotToEC.matcher(pageContent);
+		while (m.find()) {
+			result.add(createMiriamData(MiriamType.EC, m.group(1)));
+		}
+		return result;
+	}
 
 	@Override
 	public Object refreshCacheQuery(Object query) throws SourceNotAvailable {
@@ -224,6 +248,39 @@ public class UniprotAnnotator extends ElementAnnotator implements IExternalServi
 		}
 
 	}
+	
+	/**
+	 * Transform uniprot identifier into EC identifiers.
+	 * 
+	 * @param uniprot
+	 *          {@link MiriamData} with uniprot identifier
+	 * @return ArrayList of  {@link MiriamData} with EC codes
+	 * @throws UniprotSearchException
+	 *           thrown when there is a problem with accessing external database
+	 */
+	public Collection<MiriamData> uniProtToEC(MiriamData uniprot) throws UniprotSearchException {
+		if (uniprot == null) {
+			return null;
+		}
+
+		if (!MiriamType.UNIPROT.equals(uniprot.getDataType())) {
+			throw new InvalidArgumentException(MiriamType.UNIPROT + " expected.");
+		}
+
+		String accessUrl = getUniprotUrl(uniprot.getResource());
+		try {
+			String pageContent = getWebPageContent(accessUrl);
+			Collection<MiriamData> collection = parseEC(pageContent);
+			if (collection.size() > 0) {
+				return collection;
+			} else {
+				return null;
+			}
+		} catch (IOException e) {
+			throw new UniprotSearchException("Problem with accessing uniprot webpage", e);
+		}
+
+	}
 
 	@Override
 	public String getCommonName() {
diff --git a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/AllAnnotatorTests.java b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/AllAnnotatorTests.java
index 8490ffda2b88ae2645e2644d15097931bbaa988d..96735e6957fb8682aa42780c29155642fc7185fe 100644
--- a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/AllAnnotatorTests.java
+++ b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/AllAnnotatorTests.java
@@ -16,6 +16,7 @@ import org.junit.runners.Suite.SuiteClasses;
 		EntrezAnnotatorTest.class, //
 		GoAnnotatorTest.class, //
 		HgncAnnotatorTest.class, //
+		KeggAnnotatorTest.class, //
 		PdbAnnotatorTest.class, //
 		ReconAnnotatorTest.class, //
 		StitchAnnotatorTest.class, //
diff --git a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/BrendaAnnotatorTest.java b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/BrendaAnnotatorTest.java
index 963bc74fa59afa47bedd9e34a6df59dac490d5c4..a6db99a245ddbcbb729f4b6f53ddcb5dbf40f107 100644
--- a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/BrendaAnnotatorTest.java
+++ b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/BrendaAnnotatorTest.java
@@ -38,23 +38,6 @@ public class BrendaAnnotatorTest extends AnnotationTestFunctions {
 	@After
 	public void tearDown() throws Exception {
 	}
-	
-	@Test
-	public void testUniprotToBrenda() throws Exception {
-		try {
-			Collection<MiriamData> mds = brendaAnnotator.uniprotToBrenda(new MiriamData(MiriamType.UNIPROT, "P12345"));
-			assertEquals(mds.size(), 2);			
-			MiriamData md1 = new MiriamData(MiriamType.BRENDA, "2.6.1.1");
-			MiriamData md2 = new MiriamData(MiriamType.BRENDA, "2.6.1.7");
-			for (MiriamData md: mds) {
-				assertTrue(md.compareTo(md1) == 0 || md.compareTo(md2) == 0);
-			}
-			
-		} catch (Exception e) {
-			e.printStackTrace();
-			throw e;
-		}
-	}
 
 	@Test
 	public void testAnnotateFromUniprot() throws Exception {
@@ -195,29 +178,7 @@ public class BrendaAnnotatorTest extends AnnotationTestFunctions {
 			e.printStackTrace();
 			throw e;
 		}
-	}
-
-	@Test
-	public void testInvalidUniprotToCazyNull() throws Exception {
-		try {
-			assertNull(brendaAnnotator.uniprotToBrenda(null));
-		} catch (Exception e) {
-			e.printStackTrace();
-			throw e;
-		}
-	}
-
-	@Test
-	public void testInvalidUniprotToCazyWrongMd() throws Exception {
-		try {
-			brendaAnnotator.uniprotToBrenda(new MiriamData(MiriamType.WIKIPEDIA, "bla"));
-			fail("Exception expected");
-		} catch (InvalidArgumentException e) {
-		} catch (Exception e) {
-			e.printStackTrace();
-			throw e;
-		}
-	}
+	}	
 
 	@Test
 	public void testRefreshInvalidCacheQuery() throws Exception {
@@ -266,6 +227,7 @@ public class BrendaAnnotatorTest extends AnnotationTestFunctions {
 		}
 	}
 
+	/* Relying on uniprot and tair annotators which have their own tests
 	@Test
 	public void testSimulateDownStatus() throws Exception {
 		WebPageDownloader downloader = brendaAnnotator.getWebPageDownloader();
@@ -281,6 +243,7 @@ public class BrendaAnnotatorTest extends AnnotationTestFunctions {
 			brendaAnnotator.setWebPageDownloader(downloader);
 		}
 	}
+	
 
 	@Test
 	public void testSimulateChangedStatus() throws Exception {
@@ -297,5 +260,6 @@ public class BrendaAnnotatorTest extends AnnotationTestFunctions {
 			brendaAnnotator.setWebPageDownloader(downloader);
 		}
 	}
+	*/
 
 }
diff --git a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/KeggAnnotatorTest.java b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/KeggAnnotatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5a98a3a342f33001763231997a010b5eb5e511a
--- /dev/null
+++ b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/KeggAnnotatorTest.java
@@ -0,0 +1,310 @@
+package lcsb.mapviewer.annotation.services.annotators;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import lcsb.mapviewer.annotation.AnnotationTestFunctions;
+import lcsb.mapviewer.annotation.cache.WebPageDownloader;
+import lcsb.mapviewer.annotation.services.ExternalServiceStatusType;
+import lcsb.mapviewer.common.exception.InvalidArgumentException;
+import lcsb.mapviewer.model.map.MiriamData;
+import lcsb.mapviewer.model.map.MiriamType;
+import lcsb.mapviewer.model.map.species.GenericProtein;
+import lcsb.mapviewer.model.map.species.Species;
+
+public class KeggAnnotatorTest extends AnnotationTestFunctions {
+	
+	@Autowired
+	KeggAnnotator keggAnnotator;
+
+	@Before
+	public void setUp() throws Exception {
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+	
+	private void Evaluate_3_1_2_14(Species protein) {
+		
+		Collection<MiriamData> mdPubmed = new ArrayList<>();
+
+		for (MiriamData md : protein.getMiriamData()) {
+			if (md.getDataType().equals(MiriamType.PUBMED)) {
+				mdPubmed.add(md);
+			}
+		}
+		
+		assertTrue("No PUBMED annotation extracted from KEGG annotator", mdPubmed.size() > 0);
+		assertTrue("Wrong number of publications extracted from KEGG annotator", mdPubmed.size() == 2);
+		
+		Set<String> pmids = new HashSet<String>();
+		pmids.add("30409");
+		pmids.add("3134");
+		int cntMatches = 0;
+		for (MiriamData md: protein.getMiriamData()) {
+			if (pmids.contains(md.getResource())) {
+				cntMatches++;
+			}				
+		}
+		
+		assertTrue("Wrong PUBMED IDs extracted from KEGG annotator", cntMatches == 2);
+		
+	}
+	
+		
+
+	@Test
+	public void testAnnotateFromUniprot() throws Exception {
+		try {
+
+			Species protein = new GenericProtein("id");
+			protein.setName("bla");
+			protein.addMiriamData(new MiriamData(MiriamType.UNIPROT, "Q42561"));
+			
+			keggAnnotator.annotateElement(protein);
+			
+			Evaluate_3_1_2_14(protein);
+			
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+	
+	@Test
+	public void testAnnotateFromEc() throws Exception {
+		try {
+			
+			Species protein = new GenericProtein("id");
+			protein.setName("bla");
+			protein.addMiriamData(new MiriamData(MiriamType.EC, "3.1.2.14"));
+			
+			keggAnnotator.annotateElement(protein);
+			
+			Evaluate_3_1_2_14(protein);
+			
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+	
+	@Test
+	public void testAnnotateFromTair() throws Exception {
+		try {
+			Species protein = new GenericProtein("id");
+			protein.setName("bla");
+			protein.addMiriamData(new MiriamData(MiriamType.TAIR_LOCUS, "AT3G25110"));
+			
+			keggAnnotator.annotateElement(protein);
+			
+			Evaluate_3_1_2_14(protein);
+			
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}	
+	
+	
+	@Test
+	public void testAnnotateFromUniprotWithMultipleECs() throws Exception {
+		try {
+			Species protein = new GenericProtein("id");
+			protein.setName("bla");
+			protein.addMiriamData(new MiriamData(MiriamType.UNIPROT, "P12345"));
+			
+			keggAnnotator.annotateElement(protein);
+			
+			Collection<MiriamData> mdPubmed = new ArrayList<>();
+
+			for (MiriamData md : protein.getMiriamData()) {
+				if (md.getDataType().equals(MiriamType.PUBMED)) {
+					mdPubmed.add(md);
+				}
+			}
+			
+			assertTrue("Wrong number of publications extracted from KEGG annotator", mdPubmed.size() == 9);			
+			
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}		
+	}
+	
+	@Test
+	public void testAnnotateFromUniprotWithMultipleECsAndEC() throws Exception {
+		try {
+			Species protein = new GenericProtein("id");
+			protein.setName("bla");
+			protein.addMiriamData(new MiriamData(MiriamType.UNIPROT, "P12345"));
+			protein.addMiriamData(new MiriamData(MiriamType.EC, "3.1.2.14"));
+			
+			keggAnnotator.annotateElement(protein);
+			
+			Collection<MiriamData> mdPubmed = new ArrayList<>();
+
+			for (MiriamData md : protein.getMiriamData()) {
+				if (md.getDataType().equals(MiriamType.PUBMED)) {
+					mdPubmed.add(md);
+				}
+			}
+			
+			assertTrue("Wrong number of publications extracted from KEGG annotator", mdPubmed.size() == 11);			
+			
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}		
+	}
+	
+	@Test
+	public void testAnnotateInvalidEmpty() throws Exception {
+		try {
+			Species protein = new GenericProtein("id");
+			protein.setName("bla");
+			keggAnnotator.annotateElement(protein);
+
+			assertEquals(0, protein.getMiriamData().size());
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+	
+	@Test
+	public void testAnnotateInvalidUniprot() throws Exception {
+		try {
+			Species protein = new GenericProtein("id");
+			protein.addMiriamData(new MiriamData(MiriamType.UNIPROT, "bla"));
+			keggAnnotator.annotateElement(protein);
+
+			assertEquals(1, protein.getMiriamData().size());
+
+			assertEquals(1, getWarnings().size());
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+	
+	@Test
+	public void testAnnotateInvalidTair() throws Exception {
+		try {
+			Species protein = new GenericProtein("id");
+			protein.addMiriamData(new MiriamData(MiriamType.TAIR_LOCUS, "bla"));
+			keggAnnotator.annotateElement(protein);
+
+			assertEquals(1, protein.getMiriamData().size());
+
+			assertEquals(1, getWarnings().size());
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@Test
+	public void testRefreshInvalidCacheQuery() throws Exception {
+		try {
+			keggAnnotator.refreshCacheQuery("invalid_query");
+			fail("Exception expected");
+		} catch (InvalidArgumentException e) {
+			assertTrue(e.getMessage().contains("Don't know what to do"));
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@Test
+	public void testRefreshInvalidCacheQuery2() throws Exception {
+		try {
+			keggAnnotator.refreshCacheQuery(new Object());
+			fail("Exception expected");
+		} catch (InvalidArgumentException e) {
+			assertTrue(e.getMessage().contains("Don't know what to do"));
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@Test
+	public void testRefreshCacheQuery() throws Exception {
+		try {
+			Object res = keggAnnotator.refreshCacheQuery("http://google.cz/");
+			assertNotNull(res);
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@Test
+	public void testStatus() throws Exception {
+		try {
+			assertEquals(ExternalServiceStatusType.OK, keggAnnotator.getServiceStatus().getStatus());
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@Test
+	public void testSimulateDownStatus() throws Exception {
+		WebPageDownloader downloader = keggAnnotator.getWebPageDownloader();
+		try {
+			WebPageDownloader mockDownloader = Mockito.mock(WebPageDownloader.class);
+			when(mockDownloader.getFromNetwork(anyString(), anyString(), anyString())).thenThrow(new IOException());
+			keggAnnotator.setWebPageDownloader(mockDownloader);
+			assertEquals(ExternalServiceStatusType.DOWN, keggAnnotator.getServiceStatus().getStatus());
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		} finally {
+			keggAnnotator.setWebPageDownloader(downloader);
+		}
+	}
+
+	@Test
+	public void testSimulateChangedStatus() throws Exception {
+		WebPageDownloader downloader = keggAnnotator.getWebPageDownloader();
+		try {
+			WebPageDownloader mockDownloader = Mockito.mock(WebPageDownloader.class);
+			when(mockDownloader.getFromNetwork(anyString(), anyString(), anyString())).thenReturn("GN   Name=ACSS2; Synonyms=ACAS2;");
+			keggAnnotator.setWebPageDownloader(mockDownloader);
+			assertEquals(ExternalServiceStatusType.CHANGED, keggAnnotator.getServiceStatus().getStatus());
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		} finally {
+			keggAnnotator.setWebPageDownloader(downloader);
+		}
+	}
+
+}
diff --git a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/UniprotAnnotatorTest.java b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/UniprotAnnotatorTest.java
index c27be152ae03470035eab296978d066bcdcd7488..aec70cf429c87b9fcc0a8c64696a9629e51ac64a 100644
--- a/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/UniprotAnnotatorTest.java
+++ b/annotation/src/test/java/lcsb/mapviewer/annotation/services/annotators/UniprotAnnotatorTest.java
@@ -9,6 +9,8 @@ import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.when;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
 
 import org.junit.After;
 import org.junit.Before;
@@ -45,7 +47,7 @@ public class UniprotAnnotatorTest extends AnnotationTestFunctions {
 		try {
 
 			Species protein = new GenericProtein("id");
-			protein.setName("P01308");
+			protein.setName("P12345");
 			uniprotAnnotator.annotateElement(protein);
 
 			assertTrue(protein.getMiriamData().size() > 0);
@@ -53,6 +55,7 @@ public class UniprotAnnotatorTest extends AnnotationTestFunctions {
 			boolean entrez = false;
 			boolean hgnc = false;
 			boolean uniprot = false;
+			boolean ec = false;
 
 			for (MiriamData md : protein.getMiriamData()) {
 				if (md.getDataType().equals(MiriamType.UNIPROT)) {
@@ -61,11 +64,14 @@ public class UniprotAnnotatorTest extends AnnotationTestFunctions {
 					hgnc = true;
 				} else if (md.getDataType().equals(MiriamType.ENTREZ)) {
 					entrez = true;
+				} else if (md.getDataType().equals(MiriamType.EC)) {
+					ec = true;
 				}
 			}
 			assertTrue("No HGNC annotation extracted from uniprot annotator", hgnc);
 			assertTrue("No ENTREZ annotation extracted from uniprot annotator", entrez);
 			assertTrue("No UNIPROT annotation extracted from uniprot annotator", uniprot);
+			assertTrue("No UNIPROT annotation extracted from uniprot annotator", ec);
 
 		} catch (Exception e) {
 			e.printStackTrace();
@@ -73,6 +79,63 @@ public class UniprotAnnotatorTest extends AnnotationTestFunctions {
 		}
 
 	}
+	
+	@Test
+	public void testEC1() throws Exception {
+		try {
+			
+			Collection<MiriamData> mds = uniprotAnnotator.uniProtToEC(new MiriamData(MiriamType.UNIPROT, "P12345"));
+
+			assertEquals(mds.size(), 2);			
+			MiriamData md1 = new MiriamData(MiriamType.EC, "2.6.1.1");
+			MiriamData md2 = new MiriamData(MiriamType.EC, "2.6.1.7");
+			for (MiriamData md: mds) {
+				assertTrue(md.compareTo(md1) == 0 || md.compareTo(md2) == 0);
+			}
+			
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+
+	}
+	
+	@Test
+	public void testEC2() throws Exception {
+		try {
+			
+			Collection<MiriamData> mds = uniprotAnnotator.uniProtToEC(new MiriamData(MiriamType.UNIPROT, "P25405"));
+
+			assertTrue("No EC miriam data extracted from uniprot annotator", mds.size() > 0);
+			
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+
+	}
+	
+	@Test
+	public void testInvalidUniprotToECNull() throws Exception {
+		try {
+			assertNull(uniprotAnnotator.uniProtToEC(null));
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@Test
+	public void testInvalidUniprotToECWrongMd() throws Exception {
+		try {
+			uniprotAnnotator.uniProtToEC(new MiriamData(MiriamType.WIKIPEDIA, "bla"));
+			fail("Exception expected");
+		} catch (InvalidArgumentException e) {
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
 
 	@Test
 	public void testAnnotateInvalid2() throws Exception {
diff --git a/frontend-js/src/main/js/gui/leftPanel/GuiUtils.js b/frontend-js/src/main/js/gui/leftPanel/GuiUtils.js
index ea45176d138344c93f88c9033b10f0f2d4803b31..27878f32532bc4cc2877497851d045b596625a8b 100644
--- a/frontend-js/src/main/js/gui/leftPanel/GuiUtils.js
+++ b/frontend-js/src/main/js/gui/leftPanel/GuiUtils.js
@@ -181,38 +181,108 @@ GuiUtils.prototype.createAnnotationList = function (annotations, options) {
 
   var self = this;
   var result = document.createElement("div");
+
+  var grouppedAnnotations = {};
   for (var i = 0; i < annotations.length; i++) {
-    var element = annotations[i];
-    var link = self.createAnnotationLink(element, showType);
-    if (inline) {
-      if (i > 0) {
-        var coma = document.createElement("span");
-        coma.innerHTML = ", ";
-        result.appendChild(coma);
-      }
-      result.appendChild(link);
-    } else {
+    var desc = annotations[i].description;
+    if (!(desc in grouppedAnnotations)) grouppedAnnotations[desc] = [];
+    grouppedAnnotations[desc].push(annotations[i])
+  }
+
+  var cntAnnotations = 0;
+  for (var i = 0; i < grouppedAnnotations.keys().length; i++) {
+
+    var desc = grouppedAnnotations.keys()[i];
+    var groupContainer = (inline ? document.createElement("span") : document.createElement("div"));
+    var descContainer = (inline ? document.createElement("span") : document.createElement("div"));
+    descContainer.innerHTML = (inline ? desc + ': ' : desc);
+    if (inline) groupContainer.className = "minerva-annotation-group";
+    groupContainer.appendChild(descContainer);
+
+    if (inline){
+      var par = document.createElement("span");
+      par.innerHTML = "(";
+      groupContainer.appendChild(par);
+    }
 
-      var row = document.createElement("div");
-      row.style.height = "26px";
-      if (i % 2 === 0) {
-        row.className = "minerva-annotation-row-odd";
+    for (var j = 0; j < grouppedAnnotations[desc].length; j++) {
+
+      cntAnnotations += 1;
+
+      var element = grouppedAnnotations[desc][j];
+      var link = self.createAnnotationLink(element, showType);
+      if (inline) {
+        if (j > 0) {
+            var coma = document.createElement("span");
+            coma.innerHTML = ", ";
+            groupContainer.appendChild(coma);
+        }
+        groupContainer.appendChild(link);
       } else {
-        row.className = "minerva-annotation-row-even";
+
+        var row = document.createElement("div");
+        row.style.height = "26px";
+        if (i % 2 === 0) {
+            row.className = "minerva-annotation-row-odd";
+        } else {
+            row.className = "minerva-annotation-row-even";
+        }
+
+        var header = document.createElement("div");
+        header.style.width = "24px";
+        header.style.float = "left";
+        header.innerHTML = "[" + cntAnnotations + "]";
+        row.appendChild(header);
+
+        var body = document.createElement("div");
+        body.style.float = "left";
+        body.appendChild(link);
+        row.appendChild(body);
+        groupContainer.appendChild(row);
       }
-      var header = document.createElement("div");
-      header.style.width = "24px";
-      header.style.float = "left";
-      header.innerHTML = "[" + (i + 1) + "]";
-      row.appendChild(header);
-
-      var body = document.createElement("div");
-      body.style.float = "left";
-      body.appendChild(link);
-      row.appendChild(body);
-      result.appendChild(row);
     }
-  }
+
+    if (inline){
+        var par = document.createElement("span");
+        par.innerHTML = ")";
+        groupContainer.appendChild(par);
+    }
+
+    result.appendChild(groupContainer);
+  }
+
+  // for (var i = 0; i < annotations.length; i++) {
+  //   var element = annotations[i];
+  //   var link = self.createAnnotationLink(element, showType);
+  //   if (inline) {
+  //     if (i > 0) {
+  //       var coma = document.createElement("span");
+  //       coma.innerHTML = ", ";
+  //       result.appendChild(coma);
+  //     }
+  //     result.appendChild(link);
+  //   } else {
+  //
+  //     var row = document.createElement("div");
+  //     row.style.height = "26px";
+  //     if (i % 2 === 0) {
+  //       row.className = "minerva-annotation-row-odd";
+  //     } else {
+  //       row.className = "minerva-annotation-row-even";
+  //     }
+  //     var header = document.createElement("div");
+  //     header.style.width = "24px";
+  //     header.style.float = "left";
+  //     header.innerHTML = "[" + (i + 1) + "]";
+  //     row.appendChild(header);
+  //
+  //     var body = document.createElement("div");
+  //     body.style.float = "left";
+  //     body.appendChild(link);
+  //     row.appendChild(body);
+  //     result.appendChild(row);
+  //   }
+  // }
   return result;
 };
 
diff --git a/persist/src/db/12.0.0/fix_db_20180125.sql b/persist/src/db/12.0.0/fix_db_20180125.sql
new file mode 100644
index 0000000000000000000000000000000000000000..dcc8ee57e39e438781ef7a05e2633d6774345537
--- /dev/null
+++ b/persist/src/db/12.0.0/fix_db_20180125.sql
@@ -0,0 +1,3 @@
+DELETE FROM cache_type WHERE classname = 'lcsb.mapviewer.annotation.services.annotators.KeggAnnotator';
+INSERT INTO cache_type(validity, classname) VALUES (365, 'lcsb.mapviewer.annotation.services.annotators.KeggAnnotator');
+