diff --git a/lang/en/cache.php b/lang/en/cache.php
index 6c34d4706c4..a8005aee283 100644
--- a/lang/en/cache.php
+++ b/lang/en/cache.php
@@ -60,6 +60,7 @@ $string['cachedef_plugin_manager'] = 'Plugin info manager';
 $string['cachedef_questiondata'] = 'Question definitions';
 $string['cachedef_repositories'] = 'Repositories instances data';
 $string['cachedef_search_results'] = 'Search results user data';
+$string['cachedef_grade_categories'] = 'Grade category queries';
 $string['cachedef_string'] = 'Language string cache';
 $string['cachedef_tags'] = 'Tags collections and areas';
 $string['cachedef_userselections'] = 'Data used to persist user selections throughout Moodle';
diff --git a/lib/db/caches.php b/lib/db/caches.php
index 7f4fc16ae9f..f9b0bd6c7e4 100644
--- a/lib/db/caches.php
+++ b/lib/db/caches.php
@@ -271,4 +271,9 @@ $definitions = array(
         'staticaccelerationsize' => 3
     ),
 
+    // Grade categories. Stored at request level as invalidation is very aggressive.
+    'grade_categories' => array(
+        'mode' => cache_store::MODE_REQUEST,
+        'simplekeys' => true,
+    ),
 );
diff --git a/lib/grade/grade_category.php b/lib/grade/grade_category.php
index 672fc08f2f4..b5d64185fc8 100644
--- a/lib/grade/grade_category.php
+++ b/lib/grade/grade_category.php
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-require_once('grade_object.php');
+require_once(__DIR__ . '/grade_object.php');
 
 /**
  * grade_category is an object mapped to DB table {prefix}grade_categories
@@ -188,7 +188,22 @@ class grade_category extends grade_object {
      * @return grade_category The retrieved grade_category instance or false if none found.
      */
     public static function fetch($params) {
-        return grade_object::fetch_helper('grade_categories', 'grade_category', $params);
+        if ($records = self::retrieve_record_set($params)) {
+            return reset($records);
+        }
+
+        $record = grade_object::fetch_helper('grade_categories', 'grade_category', $params);
+
+        // We store it as an array to keep a key => result set interface in the cache, grade_object::fetch_helper is
+        // managing exceptions. We return only the first element though.
+        $records = false;
+        if ($record) {
+            $records = array($record->id => $record);
+        }
+
+        self::set_record_set($params, $records);
+
+        return $record;
     }
 
     /**
@@ -198,7 +213,14 @@ class grade_category extends grade_object {
      * @return array array of grade_category insatnces or false if none found.
      */
     public static function fetch_all($params) {
-        return grade_object::fetch_all_helper('grade_categories', 'grade_category', $params);
+        if ($records = self::retrieve_record_set($params)) {
+            return $records;
+        }
+
+        $records = grade_object::fetch_all_helper('grade_categories', 'grade_category', $params);
+        self::set_record_set($params, $records);
+
+        return $records;
     }
 
     /**
@@ -2604,4 +2626,68 @@ class grade_category extends grade_object {
 
         return $defaultcoefficients;
     }
+
+    /**
+     * Cleans the cache.
+     *
+     * We invalidate them all so it can be completely reloaded.
+     *
+     * Being conservative here, if there is a new grade_category we purge them, the important part
+     * is that this is not purged when there are no changes in grade_categories.
+     *
+     * @param bool $deleted
+     * @return void
+     */
+    protected function notify_changed($deleted) {
+        self::clean_record_set();
+    }
+
+    /**
+     * Generates a unique key per query.
+     *
+     * Not unique between grade_object children. self::retrieve_record_set and self::set_record_set will be in charge of
+     * selecting the appropriate cache.
+     *
+     * @param array $params An array of conditions like $fieldname => $fieldvalue
+     * @return string
+     */
+    protected static function generate_record_set_key($params) {
+        return sha1(json_encode($params));
+    }
+
+    /**
+     * Tries to retrieve a record set from the cache.
+     *
+     * @param array $params The query params
+     * @return grade_object[]|bool An array of grade_objects or false if not found.
+     */
+    protected static function retrieve_record_set($params) {
+        $cache = cache::make('core', 'grade_categories');
+        return $cache->get(self::generate_record_set_key($params));
+    }
+
+    /**
+     * Sets a result to the records cache, even if there were no results.
+     *
+     * @param string $params The query params
+     * @param grade_object[]|bool $records An array of grade_objects or false if there are no records matching the $key filters
+     * @return void
+     */
+    protected static function set_record_set($params, $records) {
+        $cache = cache::make('core', 'grade_categories');
+        return $cache->set(self::generate_record_set_key($params), $records);
+    }
+
+    /**
+     * Cleans the cache.
+     *
+     * Aggressive deletion to be conservative given the gradebook design.
+     * The key is based on the requested params, not easy nor worth to purge selectively.
+     *
+     * @return void
+     */
+    public static function clean_record_set() {
+        $cache = cache::make('core', 'grade_categories');
+        $cache->purge();
+    }
 }
diff --git a/version.php b/version.php
index 6b690801067..13c48cd6288 100644
--- a/version.php
+++ b/version.php
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2016031000.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2016031000.01;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.