diff --git a/common/source/java/ch/systemsx/cisd/common/parser/MemorySizeFormatter.java b/common/source/java/ch/systemsx/cisd/common/parser/MemorySizeFormatter.java
new file mode 100644
index 0000000000000000000000000000000000000000..c678d17e75558a05d4d8a13b8dc95516c658be54
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/parser/MemorySizeFormatter.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2015 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.parser;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.FileUtils;
+
+/**
+ * @author pkupczyk
+ */
+public class MemorySizeFormatter
+{
+
+    public static long parse(String size)
+    {
+        for (Format format : Format.values())
+        {
+            if (format.canParse(size))
+            {
+                return format.parse(size);
+            }
+        }
+
+        throw new IllegalArgumentException("Could not parse memory size value: " + size);
+    }
+
+    public static String format(long size)
+    {
+        for (Format format : Format.values())
+        {
+            if (format.canFormat(size))
+            {
+                return format.format(size);
+            }
+        }
+
+        throw new IllegalArgumentException("Could not format memory size value: " + size);
+    }
+
+    private enum Format
+    {
+
+        ZERO("(0+)\\s*(|b|B)", "", 0)
+        {
+            @Override
+            public boolean canFormat(long size)
+            {
+                return size == 0;
+            }
+        },
+        BYTE("([0-9]+)\\s*(|b|B)", "b", 0),
+        KILO("([0-9]+)\\s*(k|K)", "k", 1),
+        MEGA("([0-9]+)\\s*(m|M)", "m", 2),
+        GIGA("([0-9]+)\\s*(g|G)", "g", 3),
+        TERA("([0-9]+)\\s*(t|T)", "t", 4)
+        {
+            @Override
+            public boolean canFormat(long size)
+            {
+                return size > 0;
+            }
+        };
+
+        private Pattern parsePattern;
+
+        private String formatUnit;
+
+        private int power;
+
+        Format()
+        {
+        }
+
+        Format(String parsePattern, String formatUnit, int power)
+        {
+            this.parsePattern = Pattern.compile(parsePattern);
+            this.formatUnit = formatUnit;
+            this.power = power;
+        }
+
+        public boolean canParse(String size)
+        {
+            return parsePattern.matcher(size).matches();
+        }
+
+        public long parse(String size)
+        {
+            Matcher m = parsePattern.matcher(size);
+            m.matches();
+            long value = Long.valueOf(m.group(1));
+            long factor = (long) Math.pow(FileUtils.ONE_KB, power);
+            return value * factor;
+        }
+
+        public boolean canFormat(long size)
+        {
+            long lowerBound = (long) Math.pow(FileUtils.ONE_KB, power);
+            long upperBound = (long) Math.pow(FileUtils.ONE_KB, power + 1);
+            return lowerBound <= size && size < upperBound;
+        }
+
+        public String format(long size)
+        {
+            double factor = Math.pow(FileUtils.ONE_KB, power);
+            double value = size / factor;
+
+            if (Math.abs(value - Math.round(value)) < 0.1)
+            {
+                return String.format("%d", (long) value) + formatUnit;
+            } else
+            {
+                return String.format("%.1f", value) + formatUnit;
+            }
+
+        }
+    }
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/PercentFormatter.java b/common/source/java/ch/systemsx/cisd/common/parser/PercentFormatter.java
new file mode 100644
index 0000000000000000000000000000000000000000..90c9c81233409c2774898643a64f6fe26f22ab9f
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/parser/PercentFormatter.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.parser;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author pkupczyk
+ */
+public class PercentFormatter
+{
+
+    private static final Pattern pattern = Pattern.compile("([0-9]+)\\s*\\%");
+
+    public static int parse(String percent)
+    {
+        Matcher m = pattern.matcher(percent);
+
+        if (m.matches())
+        {
+            return Integer.valueOf(m.group(1));
+        } else
+        {
+            throw new IllegalArgumentException("Could not parse percent value: " + percent);
+        }
+    }
+
+    public static String format(int percent)
+    {
+        if (percent < 0)
+        {
+            throw new IllegalArgumentException("Percent value has to greater than or equal to 0");
+        }
+        return percent + "%";
+    }
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/test/AssertionUtil.java b/common/source/java/ch/systemsx/cisd/common/test/AssertionUtil.java
index 69cb03462ceee679e093853ae4f0263c661bbcd5..17323077fc957ba536c758c29fbc7fa90b1f5519 100644
--- a/common/source/java/ch/systemsx/cisd/common/test/AssertionUtil.java
+++ b/common/source/java/ch/systemsx/cisd/common/test/AssertionUtil.java
@@ -73,6 +73,13 @@ public class AssertionUtil
         assertTrue(errorMsg, collection.size() == size);
     }
 
+    public static void assertMatches(String expectedRegexp, String actual)
+    {
+        String errorMsg =
+                String.format("String:\n'%s'\nwas expected to match the following regexp:\n'%s'", actual, expectedRegexp);
+        assertTrue(errorMsg, actual != null && actual.matches(expectedRegexp));
+    }
+
     /** asserts that given text contains expectedSubstring. Comparision is case insensitive. */
     public static void assertContainsInsensitive(String expectedSubstring, String text)
     {
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/parser/MemorySizeFormatterTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/parser/MemorySizeFormatterTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..953aafab4fdaba1df70ae9a86a40c5b7e3c76e61
--- /dev/null
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/parser/MemorySizeFormatterTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2015 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.parser;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.commons.io.FileUtils;
+import org.testng.annotations.Test;
+
+/**
+ * @author pkupczyk
+ */
+public class MemorySizeFormatterTest
+{
+
+    @Test(expectedExceptions = { IllegalArgumentException.class })
+    public void testFormatNegative()
+    {
+        MemorySizeFormatter.format(-1);
+    }
+
+    @Test
+    public void testFormatZero()
+    {
+        testFormat(0, "0");
+    }
+
+    @Test
+    public void testFormatBytesLowerBound()
+    {
+        testFormat(1, "1b");
+    }
+
+    @Test
+    public void testFormatBytesUpperBound()
+    {
+        testFormat(FileUtils.ONE_KB - 1, "1023b");
+    }
+
+    @Test
+    public void testFormatKilobytesLowerBound()
+    {
+        testFormat(FileUtils.ONE_KB, "1k");
+    }
+
+    @Test
+    public void testFormatKilobytesUpperBound()
+    {
+        testFormat(FileUtils.ONE_MB - 1, "1023k");
+    }
+
+    @Test
+    public void testFormatMegabytesLowerBound()
+    {
+        testFormat(FileUtils.ONE_MB, "1m");
+    }
+
+    @Test
+    public void testFormatMegabytesUpperBound()
+    {
+        testFormat(FileUtils.ONE_GB - 1, "1023m");
+    }
+
+    @Test
+    public void testFormatGigabytesLowerBound()
+    {
+        testFormat(FileUtils.ONE_GB, "1g");
+    }
+
+    @Test
+    public void testFormatGigabytesUpperBound()
+    {
+        testFormat(FileUtils.ONE_GB * FileUtils.ONE_KB - 1, "1023g");
+    }
+
+    @Test
+    public void testFormatTerabytesLowerBound()
+    {
+        testFormat(FileUtils.ONE_GB * FileUtils.ONE_KB, "1t");
+    }
+
+    @Test
+    public void testFormatTerabytesNoUpperBound()
+    {
+        testFormat(FileUtils.ONE_GB * FileUtils.ONE_KB * FileUtils.ONE_KB * 2, "2048t");
+    }
+
+    @Test(expectedExceptions = { IllegalArgumentException.class })
+    public void testParseNegative()
+    {
+        MemorySizeFormatter.parse("-1");
+    }
+
+    @Test
+    public void testParseZero()
+    {
+        testParse(0, "0");
+    }
+
+    @Test
+    public void testParseWithoutUnit()
+    {
+        testParse(1, "1");
+    }
+
+    @Test
+    public void testParseWithByteUnit()
+    {
+        testParse(1, "1b", "1B", "1 b", "1  B");
+    }
+
+    @Test
+    public void testParseWithKilobyteUnit()
+    {
+        testParse(FileUtils.ONE_KB, "1k", "1K", "1 k", "1  K");
+    }
+
+    @Test
+    public void testParseWithMegabyteUnit()
+    {
+        testParse(FileUtils.ONE_MB, "1m", "1M", "1 m", "1  M");
+    }
+
+    @Test
+    public void testParseWithGigabyteUnit()
+    {
+        testParse(FileUtils.ONE_GB, "1g", "1G", "1 g", "1  G");
+    }
+
+    @Test
+    public void testParseWithTerabyteUnit()
+    {
+        testParse(FileUtils.ONE_GB * FileUtils.ONE_KB, "1t", "1T", "1 t", "1  T");
+    }
+
+    private void testFormat(long bytes, String str)
+    {
+        assertEquals(MemorySizeFormatter.format(bytes), str);
+    }
+
+    private void testParse(long bytes, String... strs)
+    {
+        for (String str : strs)
+        {
+            assertEquals(MemorySizeFormatter.parse(str), bytes);
+        }
+    }
+
+}
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/parser/PercentFormatterTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/parser/PercentFormatterTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..26368d1057f8211b5114296f1248e27affb91376
--- /dev/null
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/parser/PercentFormatterTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.parser;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+/**
+ * @author pkupczyk
+ */
+public class PercentFormatterTest
+{
+
+    @Test(expectedExceptions = { IllegalArgumentException.class })
+    public void testParseNegative()
+    {
+        PercentFormatter.parse("-1%");
+    }
+
+    @Test
+    public void testParse()
+    {
+        testParse(15, "15%", "15 %");
+    }
+
+    @Test(expectedExceptions = { IllegalArgumentException.class })
+    public void testParseIncorrect()
+    {
+        PercentFormatter.parse("xyz");
+    }
+
+    @Test(expectedExceptions = { IllegalArgumentException.class })
+    public void testFormatNegative()
+    {
+        PercentFormatter.format(-1);
+    }
+
+    @Test
+    public void testFormat()
+    {
+        testFormat(15, "15%");
+    }
+
+    private void testFormat(int percent, String str)
+    {
+        assertEquals(PercentFormatter.format(percent), str);
+    }
+
+    private void testParse(int percent, String... strs)
+    {
+        for (String str : strs)
+        {
+            assertEquals(PercentFormatter.parse(str), percent);
+        }
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/ISearchCache.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/ISearchCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..bae72de78e0084831b4937e1495ba50a056d4890
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/ISearchCache.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.ethz.sis.openbis.generic.server.api.v3.cache;
+
+/**
+ * @author pkupczyk
+ */
+public interface ISearchCache<CRITERIA, FETCH_OPTIONS, OBJECT>
+{
+
+    public void put(SearchCacheKey<CRITERIA, FETCH_OPTIONS> key, SearchCacheEntry<OBJECT> entry);
+
+    public SearchCacheEntry<OBJECT> get(SearchCacheKey<CRITERIA, FETCH_OPTIONS> key);
+
+    public void remove(SearchCacheKey<CRITERIA, FETCH_OPTIONS> key);
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCache.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..c323d54f4715f8c667a566f7a1757fdf5785f437
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCache.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2015 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.ethz.sis.openbis.generic.server.api.v3.cache;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+import net.sf.ehcache.config.CacheConfiguration;
+import net.sf.ehcache.config.CacheConfiguration.CacheEventListenerFactoryConfiguration;
+import net.sf.ehcache.config.MemoryUnit;
+import net.sf.ehcache.config.PersistenceConfiguration;
+import net.sf.ehcache.config.PersistenceConfiguration.Strategy;
+import net.sf.ehcache.config.SizeOfPolicyConfiguration;
+import net.sf.ehcache.config.SizeOfPolicyConfiguration.MaxDepthExceededBehavior;
+import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
+
+import org.apache.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+import ch.systemsx.cisd.common.parser.MemorySizeFormatter;
+import ch.systemsx.cisd.common.parser.PercentFormatter;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+public class SearchCache<CRITERIA, FETCH_OPTIONS, OBJECT> implements ISearchCache<CRITERIA, FETCH_OPTIONS, OBJECT>
+{
+
+    private final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, SearchCache.class);
+
+    private static final String CACHE_NAME = "searchCache";
+
+    public static final String CACHE_SIZE_PROPERTY_NAME = "ch.ethz.sis.openbis.v3.searchcache.size";
+
+    @Autowired
+    private CacheManager cacheManager;
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public SearchCacheEntry<OBJECT> get(SearchCacheKey<CRITERIA, FETCH_OPTIONS> key)
+    {
+        Element element = getCache().get(key);
+
+        if (element != null)
+        {
+            return (SearchCacheEntry<OBJECT>) element.getObjectValue();
+        } else
+        {
+            return null;
+        }
+    }
+
+    @Override
+    public void put(SearchCacheKey<CRITERIA, FETCH_OPTIONS> key, SearchCacheEntry<OBJECT> entry)
+    {
+        getCache().put(new Element(key, entry));
+    }
+
+    @Override
+    public void remove(SearchCacheKey<CRITERIA, FETCH_OPTIONS> key)
+    {
+        getCache().remove(key);
+    }
+
+    private synchronized Cache getCache()
+    {
+        Cache cache = getCacheManager().getCache(CACHE_NAME);
+
+        if (cache == null)
+        {
+            operationLog.info("Creating the cache");
+
+            CacheConfiguration config = new CacheConfiguration();
+            config.setName(CACHE_NAME);
+            config.setEternal(false);
+            config.maxBytesLocalHeap(getCacheSize(), MemoryUnit.BYTES);
+            config.persistence(new PersistenceConfiguration().strategy(Strategy.NONE));
+            config.setTimeToIdleSeconds(3600);
+            config.setTimeToLiveSeconds(0);
+            config.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU);
+            config.addCacheEventListenerFactory(new CacheEventListenerFactoryConfiguration().className(SearchCacheEventListenerFactory.class
+                    .getName()));
+            config.sizeOfPolicy(new SizeOfPolicyConfiguration().maxDepth(10000000).maxDepthExceededBehavior(MaxDepthExceededBehavior.CONTINUE));
+
+            cache = new Cache(config);
+            getCacheManager().addCache(cache);
+        }
+
+        return cache;
+    }
+
+    private long getCacheSize()
+    {
+        String propertyValue = getSystemProperty(CACHE_SIZE_PROPERTY_NAME);
+
+        if (propertyValue == null || propertyValue.trim().length() == 0)
+        {
+            return getCacheDefaultSize();
+        } else
+        {
+            try
+            {
+                long cacheSize = MemorySizeFormatter.parse(propertyValue);
+                operationLog.info("Cache size was set to '" + propertyValue + "' in '" + CACHE_SIZE_PROPERTY_NAME + "' system property.");
+                return cacheSize;
+            } catch (IllegalArgumentException e1)
+            {
+                try
+                {
+                    int cachePercent = PercentFormatter.parse(propertyValue);
+                    long cacheSize = (long) ((cachePercent / 100.0) * getMemorySize());
+                    operationLog.info("Cache size was set to '" + propertyValue + "' in '" + CACHE_SIZE_PROPERTY_NAME
+                            + "' system property. The memory available to the JVM is " + MemorySizeFormatter.format(getMemorySize())
+                            + " which gives a cache size of " + MemorySizeFormatter.format(cacheSize));
+                    return cacheSize;
+                } catch (IllegalArgumentException e2)
+                {
+                    operationLog.warn("Cache size was set to '" + propertyValue + "' in '" + CACHE_SIZE_PROPERTY_NAME
+                            + "' system property. This value is incorrect. Please set the property to an absolute value like '512m' or '1g'."
+                            + " You can also use a value like '25%' to set the cache size relative to the memory available to the JVM.");
+                    return getCacheDefaultSize();
+                }
+            }
+        }
+    }
+
+    private long getCacheDefaultSize()
+    {
+        long memorySize = getMemorySize();
+        long cacheSize = memorySize / 4;
+        operationLog.info("Cache size has been set to its default value. The default value is 25% (" + MemorySizeFormatter.format(cacheSize) + ")"
+                + " of the memory available to the JVM (" + MemorySizeFormatter.format(memorySize) + ")."
+                + " If you would like to change this value, then please set '"
+                + CACHE_SIZE_PROPERTY_NAME + "' system property in openbis.conf file.");
+        return cacheSize;
+    }
+
+    protected CacheManager getCacheManager()
+    {
+        return cacheManager;
+    }
+
+    protected long getMemorySize()
+    {
+        return Runtime.getRuntime().maxMemory();
+    }
+
+    protected String getSystemProperty(String propertyName)
+    {
+        return System.getProperty(propertyName);
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCacheCleanupListener.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCacheCleanupListener.java
index d55b815a22b38ec377910fabba3d10d7899dfde1..a4e8a98a9b5e58e15115cd0a3adec0cbd51f6f0c 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCacheCleanupListener.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCacheCleanupListener.java
@@ -17,8 +17,6 @@
 package ch.ethz.sis.openbis.generic.server.api.v3.cache;
 
 import org.apache.log4j.Logger;
-import org.springframework.cache.Cache;
-import org.springframework.cache.Cache.ValueWrapper;
 
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
@@ -32,11 +30,11 @@ public class SearchCacheCleanupListener<CRITERIA, FETCH_OPTIONS> implements Sess
 
     private final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, getClass());
 
-    private Cache cache;
+    private ISearchCache<CRITERIA, FETCH_OPTIONS, ?> cache;
 
     private SearchCacheKey<CRITERIA, FETCH_OPTIONS> key;
 
-    public SearchCacheCleanupListener(Cache cache, SearchCacheKey<CRITERIA, FETCH_OPTIONS> key)
+    public SearchCacheCleanupListener(ISearchCache<CRITERIA, FETCH_OPTIONS, ?> cache, SearchCacheKey<CRITERIA, FETCH_OPTIONS> key)
     {
         this.cache = cache;
         this.key = key;
@@ -45,12 +43,12 @@ public class SearchCacheCleanupListener<CRITERIA, FETCH_OPTIONS> implements Sess
     @Override
     public void cleanup()
     {
-        ValueWrapper wrapper = cache.get(key);
+        SearchCacheEntry<?> entry = cache.get(key);
 
-        if (wrapper != null)
+        if (entry != null)
         {
             operationLog.info("Clean up cached search result on logout.");
-            cache.evict(key);
+            cache.remove(key);
         }
     }
 
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/AbstractSearchMethodExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/AbstractSearchMethodExecutor.java
index c9a2d08bafd5b5d7dd5310d45fac57d356e26160..88b6ac070fd71da92e792c2ce6b4fb28a9b41d07 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/AbstractSearchMethodExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/AbstractSearchMethodExecutor.java
@@ -24,10 +24,8 @@ import java.util.Map;
 
 import org.apache.log4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.Cache;
-import org.springframework.cache.Cache.ValueWrapper;
-import org.springframework.cache.CacheManager;
 
+import ch.ethz.sis.openbis.generic.server.api.v3.cache.ISearchCache;
 import ch.ethz.sis.openbis.generic.server.api.v3.cache.SearchCacheCleanupListener;
 import ch.ethz.sis.openbis.generic.server.api.v3.cache.SearchCacheEntry;
 import ch.ethz.sis.openbis.generic.server.api.v3.cache.SearchCacheKey;
@@ -53,7 +51,7 @@ public abstract class AbstractSearchMethodExecutor<OBJECT, OBJECT_PE, CRITERIA e
     private final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, getClass());
 
     @Autowired
-    private CacheManager cacheManager;
+    private ISearchCache<CRITERIA, FETCH_OPTIONS, OBJECT> cache;
 
     protected abstract ISearchObjectExecutor<CRITERIA, OBJECT_PE> getSearchExecutor();
 
@@ -132,15 +130,8 @@ public abstract class AbstractSearchMethodExecutor<OBJECT, OBJECT_PE, CRITERIA e
         return new ArrayList<OBJECT>(objects);
     }
 
-    private Cache getCache()
-    {
-        return cacheManager.getCache("searchCache");
-    }
-
-    @SuppressWarnings("unchecked")
     private SearchCacheEntry<OBJECT> getCacheEntry(IOperationContext context, CRITERIA criteria, FETCH_OPTIONS fetchOptions)
     {
-        Cache cache = getCache();
         SearchCacheKey<CRITERIA, FETCH_OPTIONS> key =
                 new SearchCacheKey<CRITERIA, FETCH_OPTIONS>(context.getSession().getSessionToken(), criteria, fetchOptions);
         SearchCacheEntry<OBJECT> entry = null;
@@ -159,19 +150,16 @@ public abstract class AbstractSearchMethodExecutor<OBJECT, OBJECT_PE, CRITERIA e
 
             if (CacheMode.RELOAD_AND_CACHE.equals(fetchOptions.getCacheMode()))
             {
-                cache.evict(key);
+                cache.remove(key);
             }
 
-            ValueWrapper wrapper = cache.get(key);
+            entry = cache.get(key);
 
-            if (wrapper == null)
+            if (entry == null)
             {
                 entry = new SearchCacheEntry<OBJECT>();
                 cache.put(key, entry);
                 context.getSession().addCleanupListener(new SearchCacheCleanupListener<CRITERIA, FETCH_OPTIONS>(cache, key));
-            } else
-            {
-                entry = (SearchCacheEntry<OBJECT>) wrapper.get();
             }
 
             if (operationLog.isDebugEnabled())
@@ -202,7 +190,7 @@ public abstract class AbstractSearchMethodExecutor<OBJECT, OBJECT_PE, CRITERIA e
                 entry.setObjects(doSearchAndTranslate(context, criteria, fetchOptions));
                 SearchCacheKey<CRITERIA, FETCH_OPTIONS> key =
                         new SearchCacheKey<CRITERIA, FETCH_OPTIONS>(context.getSession().getSessionToken(), criteria, fetchOptions);
-                getCache().put(key, entry);
+                cache.put(key, entry);
             } else
             {
                 operationLog.info("Found cache entry " + entry.hashCode() + " that contains search result with " + entry.getObjects().size()
diff --git a/openbis/source/java/ehcache.xml b/openbis/source/java/ehcache.xml
index eae1f8d436c8bb1f947af20c9b82109cd3fa4814..92662dcb9c9895a7e080216a16665690a57cb8df 100644
--- a/openbis/source/java/ehcache.xml
+++ b/openbis/source/java/ehcache.xml
@@ -13,11 +13,4 @@
 		overflowToDisk="false" diskPersistent="false"
 		memoryStoreEvictionPolicy="LFU" />
 
-	<cache name="searchCache" eternal="false" maxBytesLocalHeap="256m"
-		overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="3600"
-		timeToLiveSeconds="0" memoryStoreEvictionPolicy="LRU">
-		<cacheEventListenerFactory class="ch.ethz.sis.openbis.generic.server.api.v3.cache.SearchCacheEventListenerFactory" />
-		<sizeOfPolicy maxDepth="10000000" maxDepthExceededBehavior="continue"/>
-	</cache>
-
 </ehcache>
\ No newline at end of file
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCacheTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCacheTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8c647faad7b57279e83dfb256d314b4a8f920e1
--- /dev/null
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCacheTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2015 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.ethz.sis.openbis.generic.server.api.v3.cache;
+
+import java.io.ByteArrayInputStream;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.UUID;
+
+import net.sf.ehcache.CacheManager;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Level;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.common.logging.BufferedAppender;
+import ch.systemsx.cisd.common.logging.LogInitializer;
+import ch.systemsx.cisd.common.test.AssertionUtil;
+import ch.systemsx.cisd.openbis.util.LogRecordingUtils;
+
+/**
+ * @author pkupczyk
+ */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class SearchCacheTest
+{
+
+    private BufferedAppender logRecorder;
+
+    @BeforeMethod
+    public void beforeMethod()
+    {
+        LogInitializer.init();
+        logRecorder = LogRecordingUtils.createRecorder("%-5p %c - %m%n", Level.DEBUG);
+    }
+
+    @AfterMethod
+    public void afterMethod(Method method)
+    {
+        logRecorder.reset();
+    }
+
+    @Test
+    public void testCreateCacheWithoutSizeSpecified()
+    {
+        SearchCache cache = createCache(FileUtils.ONE_GB, null);
+        cache.get(null);
+
+        AssertionUtil
+                .assertContainsLines(
+                        "INFO  OPERATION.SearchCache - Cache size has been set to its default value."
+                                + " The default value is 25% (256m) of the memory available to the JVM (1g)."
+                                + " If you would like to change this value, then please set 'ch.ethz.sis.openbis.v3.searchcache.size' system property in openbis.conf file."
+                        , logRecorder.getLogContent());
+    }
+
+    @Test
+    public void testCreateCacheWithAbsoluteSizeSpecified()
+    {
+        SearchCache cache = createCache(FileUtils.ONE_GB, "128m");
+        cache.get(null);
+
+        AssertionUtil
+                .assertContainsLines(
+                        "INFO  OPERATION.SearchCache - Cache size was set to '128m' in 'ch.ethz.sis.openbis.v3.searchcache.size' system property.",
+                        logRecorder.getLogContent());
+    }
+
+    @Test
+    public void testCreateCacheWithRelativeSizeSpecified()
+    {
+        SearchCache cache = createCache(FileUtils.ONE_GB, "10%");
+        cache.get(null);
+
+        AssertionUtil
+                .assertContainsLines(
+                        "INFO  OPERATION.SearchCache - Cache size was set to '10%' in 'ch.ethz.sis.openbis.v3.searchcache.size' system property."
+                                + " The memory available to the JVM is 1g which gives a cache size of 102.4m", logRecorder.getLogContent());
+    }
+
+    @Test
+    public void testCreateCacheWithIncorrectSizeSpecified()
+    {
+        SearchCache cache = createCache(FileUtils.ONE_GB, "xyz");
+        cache.get(null);
+
+        AssertionUtil
+                .assertContainsLines(
+                        "WARN  OPERATION.SearchCache - Cache size was set to 'xyz' in 'ch.ethz.sis.openbis.v3.searchcache.size' system property."
+                                + " This value is incorrect. Please set the property to an absolute value like '512m' or '1g'."
+                                + " You can also use a value like '25%' to set the cache size relative to the memory available to the JVM.",
+                        logRecorder.getLogContent());
+
+        AssertionUtil
+                .assertContainsLines(
+                        "INFO  OPERATION.SearchCache - Cache size has been set to its default value."
+                                + " The default value is 25% (256m) of the memory available to the JVM (1g)."
+                                + " If you would like to change this value, then please set 'ch.ethz.sis.openbis.v3.searchcache.size' system property in openbis.conf file."
+                        , logRecorder.getLogContent());
+    }
+
+    @Test
+    public void testPutItemsToCacheWithEvicting()
+    {
+        SearchCache cache = createCache(FileUtils.ONE_GB, "3k");
+
+        SearchCacheKey key1 = createCacheKey("session1");
+        SearchCacheEntry entry1 = createCacheEntry(FileUtils.ONE_KB);
+
+        logRecorder.resetLogContent();
+        cache.put(key1, entry1);
+        AssertionUtil.assertMatches(
+                "(?s).*Cache entry ([0-9]+) that contains search result with 1 object has been put to the cache. Cache now contains 1 entry..*",
+                logRecorder.getLogContent());
+
+        SearchCacheKey key2 = createCacheKey("session2");
+        SearchCacheEntry entry2 = createCacheEntry(FileUtils.ONE_KB);
+
+        logRecorder.resetLogContent();
+        cache.put(key2, entry2);
+        AssertionUtil.assertMatches(
+                "(?s).*Cache entry ([0-9]+) that contains search result with 1 object has been put to the cache. Cache now contains 2 entries..*",
+                logRecorder.getLogContent());
+
+        SearchCacheKey key3 = createCacheKey("session3");
+        SearchCacheEntry entry3 = createCacheEntry(FileUtils.ONE_KB);
+
+        logRecorder.resetLogContent();
+        cache.put(key3, entry3);
+
+        AssertionUtil
+                .assertMatches(
+                        "(?s).*Cache entry ([0-9]+) that contains search result with 1 object has been evicted from the cache. Cache now contains 1 entry..*",
+                        logRecorder.getLogContent());
+
+        AssertionUtil.assertMatches(
+                "(?s).*Cache entry ([0-9]+) that contains search result with 1 object has been put to the cache. Cache now contains 2 entries..*",
+                logRecorder.getLogContent());
+    }
+
+    private SearchCache createCache(final long memorySize, final String cacheSize)
+    {
+        String managerConfig = "<ehcache name='" + UUID.randomUUID() + "'></ehcache>";
+        final CacheManager manager = new CacheManager(new ByteArrayInputStream(managerConfig.getBytes()));
+
+        return new SearchCache()
+            {
+                @Override
+                protected long getMemorySize()
+                {
+                    return memorySize;
+                }
+
+                @Override
+                protected String getSystemProperty(String propertyName)
+                {
+                    if (SearchCache.CACHE_SIZE_PROPERTY_NAME.equals(propertyName))
+                    {
+                        return cacheSize;
+                    } else
+                    {
+                        return null;
+                    }
+                }
+
+                @Override
+                protected CacheManager getCacheManager()
+                {
+                    return manager;
+                }
+            };
+    }
+
+    private SearchCacheKey createCacheKey(String sessionToken)
+    {
+        return new SearchCacheKey(sessionToken, new Object(), new Object());
+    }
+
+    private SearchCacheEntry createCacheEntry(long size)
+    {
+        SearchCacheEntry entry = new SearchCacheEntry();
+        entry.setObjects(Arrays.asList(new byte[(int) size]));
+        return entry;
+    }
+}