diff --git a/gms-ui/package-lock.json b/gms-ui/package-lock.json
index e988bbac737fe4b8cd3ca5ae0aa49607569bfbe9..833f2f9ac0f3318e7afd64e7327ee165c70739ff 100644
--- a/gms-ui/package-lock.json
+++ b/gms-ui/package-lock.json
@@ -3715,6 +3715,11 @@
       "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
       "dev": true
     },
+    "debounce": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
+      "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg=="
+    },
     "debug": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
diff --git a/gms-ui/package.json b/gms-ui/package.json
index 2b6cd4b52fb816a543ea4bd377af3de87a79cadb..58559a250c0aa91b541d74e762e325a8b794f514 100644
--- a/gms-ui/package.json
+++ b/gms-ui/package.json
@@ -13,6 +13,7 @@
     "@fortawesome/vue-fontawesome": "^0.1.6",
     "bootstrap-vue": "^2.0.0-rc.27",
     "core-js": "^2.6.5",
+    "debounce": "^1.2.0",
     "vue": "^2.6.10",
     "vuex": "^3.1.1"
   },
diff --git a/gms-ui/src/api/server/index.js b/gms-ui/src/api/server/index.js
index 9b7deed748cf355d746af8d876b7362c594d8af6..73076f6817c5eee58121ea37f9afb04e3fb16496 100644
--- a/gms-ui/src/api/server/index.js
+++ b/gms-ui/src/api/server/index.js
@@ -59,7 +59,30 @@ export default {
     let url = BASE_API_URL
             + 'groups?groupId=' + input.selectedGroupId
             + '&paginatorPageSize=' + input.paginatorPageSize
-            + '&paginatorPage=' + input.paginatorPage;
+            + '&paginatorPage=' + input.paginatorPage
+            + '&onlyPanel=false';
+    if(input.searchFilter !== null) {
+      url += '&searchFilter=' + input.searchFilter;
+    }
+    return apiRequest(url, {
+      method: 'GET',
+      cache: 'no-cache',
+      credentials: 'include',
+      headers: {
+        'Content-Type': 'application/json',
+        'Accept': 'application/json',
+      }
+    });
+  },
+  fetchGroupsPanel (input) {
+    let url = BASE_API_URL
+            + 'groups?groupId=' + input.selectedGroupId
+            + '&paginatorPageSize=' + input.paginatorPageSize
+            + '&paginatorPage=' + input.paginatorPage
+            + '&onlyPanel=true';
+    if(input.searchFilter !== null) {
+      url += '&searchFilter=' + input.searchFilter;
+    }
     return apiRequest(url, {
       method: 'GET',
       cache: 'no-cache',
@@ -114,7 +137,8 @@ export default {
         newGroupName: newGroupName,
         parentGroupId: input.selectedGroupId,
         paginatorPageSize: input.paginatorPageSize,
-        paginatorPage: input.paginatorPage
+        paginatorPage: input.paginatorPage,
+        searchFilter: input.searchFilter
       })
     });
   },
@@ -131,7 +155,8 @@ export default {
       body: JSON.stringify({
         newGroupName: newGroupName,
         paginatorPageSize: input.paginatorPageSize,
-        paginatorPage: input.paginatorPage
+        paginatorPage: input.paginatorPage,
+        searchFilter: input.searchFilter
       })
     });
   },
@@ -139,6 +164,9 @@ export default {
     let url = BASE_API_URL + 'group/' + groupId
             + '?paginatorPageSize=' + input.paginatorPageSize
             + '&paginatorPage=' + input.paginatorPage;
+    if(input.searchFilter !== null) {
+      url += '&searchFilter=' + input.searchFilter;
+    }
     return apiRequest(url, {
       method: 'DELETE',
       cache: 'no-cache',
diff --git a/gms-ui/src/components/GroupsBreadcrumb.vue b/gms-ui/src/components/GroupsBreadcrumb.vue
index e443dff98fe1ca00ff3d3bcbbaf84e219a629433..11a105cd423e7cc874191c05e2652d1cc9031df0 100644
--- a/gms-ui/src/components/GroupsBreadcrumb.vue
+++ b/gms-ui/src/components/GroupsBreadcrumb.vue
@@ -40,6 +40,7 @@ export default {
   methods: {
     changeBreadcrumb: function(groupId) {
       this.input.selectedGroupId = groupId;
+      this.input.searchFilter = null;
       if(this.input.selectedTab === 'groups') {
         client.fetchGroupsTab(this.input)
           .then(model => {
diff --git a/gms-ui/src/components/GroupsPanel.vue b/gms-ui/src/components/GroupsPanel.vue
index 0010c380ad0d70a714d7cabbda03ee4220a9fb92..570310af7ce24e812c60a0e400bf9e518ede6298 100644
--- a/gms-ui/src/components/GroupsPanel.vue
+++ b/gms-ui/src/components/GroupsPanel.vue
@@ -2,7 +2,7 @@
   <b-tab title="Groups" active>
     <b-row>
       <b-col xs="12">
-        <b-form-input placeholder="Search group" v-model="groupFilter" v-on:input="filterGroups"></b-form-input>
+        <b-form-input placeholder="Search group" v-model="input.searchFilter" v-on:input="filterGroups"></b-form-input>
       </b-col>
     </b-row>
     <div id="groups-list" v-if="model.groupsPanel !== null">
@@ -34,6 +34,7 @@ import ConfirmRemoveGroupModal from './modals/ConfirmRemoveGroupModal.vue';
 import Paginator from './Paginator.vue';
 import { mapState, mapActions } from 'vuex';
 import client from 'api-client';
+import debounce from 'debounce'; // for delaying the input event (search filter)
 
 export default {
   name: 'GroupsPanel',
@@ -54,6 +55,7 @@ export default {
   methods: {
     openGroup: function(group) {
       this.$store.state.input.selectedGroupId = group.groupId;
+      this.$store.state.input.searchFilter = null;
       client.fetchGroupsTab(this.input)
         .then(model => {
           this.$store.commit('updateGroups', model);
@@ -66,14 +68,20 @@ export default {
       this.$refs.confirmRemoveGroupModal.openRemoveGroupModal(group);
     },
     filterGroups: function() {
-      console.log('filterGroups', this.groupFilter);
+      client.fetchGroupsPanel(this.input)
+        .then(panel => {
+          this.$store.commit('updateGroupsPanel', panel);
+        });
     },
     updatePagination: function() {
-      client.fetchGroupsTab(this.input)
-        .then(model => {
-          this.$store.commit('updateGroupsPanel', model.groupsPanel);
+      client.fetchGroupsPanel(this.input)
+        .then(panel => {
+          this.$store.commit('updateGroupsPanel', panel);
         });
     }
+  },
+  created: function() {
+    this.filterGroups = debounce(this.filterGroups, 500)
   }
 }
 </script>
diff --git a/gms-ui/src/store.js b/gms-ui/src/store.js
index 58d25d24360808a4c836883d0c98ee8d2a114013..152510122afecb0b606d9a63ddc9dc2065b56bab 100644
--- a/gms-ui/src/store.js
+++ b/gms-ui/src/store.js
@@ -22,7 +22,8 @@ export default new Vuex.Store({
       paginatorPageSize: 20,
       paginatorPage: 1,
       selectedTab: 'groups',
-      tabIndex: 0
+      tabIndex: 0,
+      searchFilter: null
     },
     loading: false
   },
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
index 82803dfe49ea23a29b02ac9d2be8491f5bb09d71..cb7fb79d479b9d06ef23b136cbd2165763598e7a 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
@@ -4,12 +4,13 @@ import it.inaf.ia2.gms.authn.SessionData;
 import it.inaf.ia2.gms.exception.UnauthorizedException;
 import it.inaf.ia2.gms.model.request.AddGroupRequest;
 import it.inaf.ia2.gms.model.GroupNode;
-import it.inaf.ia2.gms.model.request.TabRequest;
 import it.inaf.ia2.gms.model.response.PaginatedData;
 import it.inaf.ia2.gms.model.request.PaginatedModelRequest;
 import it.inaf.ia2.gms.model.Permission;
+import it.inaf.ia2.gms.model.request.DeleteGroupRequest;
+import it.inaf.ia2.gms.model.request.GroupsRequest;
 import it.inaf.ia2.gms.model.request.RenameGroupRequest;
-import it.inaf.ia2.gms.model.response.GroupsTabResponse;
+import it.inaf.ia2.gms.model.request.SearchFilterRequest;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.service.GroupsTreeBuilder;
@@ -46,8 +47,15 @@ public class GroupsController {
     private GroupsTabResponseBuilder groupsTabResponseBuilder;
 
     @GetMapping(value = "/groups", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
-    public ResponseEntity<GroupsTabResponse> getGroupsTab(@Valid TabRequest request) {
-        return ResponseEntity.ok(groupsTabResponseBuilder.getGroupsTab(request));
+    public ResponseEntity<?> getGroupsTab(@Valid GroupsRequest request) {
+        if (request.isOnlyPanel()) {
+            // Only groupsPanel
+            GroupEntity group = groupsService.getGroupById(request.getGroupId());
+            return ResponseEntity.ok(getGroupsPanel(group, request));
+        } else {
+            // Complete GroupsTabResponse
+            return ResponseEntity.ok(groupsTabResponseBuilder.getGroupsTab(request));
+        }
     }
 
     @PostMapping(value = "/group", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@@ -61,7 +69,7 @@ public class GroupsController {
 
         groupsService.addGroup(parent, request.getNewGroupName());
 
-        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(request.getParentGroupId(), request);
+        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent, request);
 
         return new ResponseEntity<>(groupsPanel, HttpStatus.CREATED);
     }
@@ -79,13 +87,13 @@ public class GroupsController {
 
         GroupEntity parent = groupsService.getGroupByPath(renamedGroup.getParentPath());
 
-        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent.getId(), request);
+        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent, request);
 
         return ResponseEntity.ok(groupsPanel);
     }
 
     @DeleteMapping(value = "/group/{groupId}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
-    public ResponseEntity<?> deleteGroup(@PathVariable("groupId") String groupId, PaginatedModelRequest request) {
+    public ResponseEntity<?> deleteGroup(@PathVariable("groupId") String groupId, DeleteGroupRequest request) {
 
         GroupEntity group = groupsService.getGroupById(groupId);
 
@@ -95,12 +103,12 @@ public class GroupsController {
 
         GroupEntity parent = groupsService.deleteGroup(group);
 
-        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent.getId(), request);
+        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent, request);
 
         return ResponseEntity.ok(groupsPanel);
     }
 
-    private PaginatedData<GroupNode> getGroupsPanel(String parentGroupId, PaginatedModelRequest paginatedRequest) {
-        return groupsTreeBuilder.listSubGroups(parentGroupId, session.getUserId(), paginatedRequest);
+    private <T extends PaginatedModelRequest & SearchFilterRequest> PaginatedData<GroupNode> getGroupsPanel(GroupEntity parentGroup, T request) {
+        return groupsTreeBuilder.listSubGroups(parentGroup, request, session.getUserId());
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilder.java b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilder.java
index 420fe2a70fbaaa8fc2c97393dbc0f44b6e41b11d..fb1359a1e4c762553c365c1f77671c0486ef3893 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilder.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilder.java
@@ -2,7 +2,7 @@ package it.inaf.ia2.gms.controller;
 
 import it.inaf.ia2.gms.authn.SessionData;
 import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.model.request.TabRequest;
+import it.inaf.ia2.gms.model.request.GroupsRequest;
 import it.inaf.ia2.gms.model.response.GroupsTabResponse;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.service.GroupsService;
@@ -26,7 +26,7 @@ public class GroupsTabResponseBuilder {
     @Autowired
     private GroupsTreeBuilder groupsTreeBuilder;
 
-    public GroupsTabResponse getGroupsTab(TabRequest request) {
+    public GroupsTabResponse getGroupsTab(GroupsRequest request) {
 
         GroupEntity group = groupsService.getGroupById(request.getGroupId());
 
@@ -37,7 +37,7 @@ public class GroupsTabResponseBuilder {
         Permission currentNodePermissions = permissionsService.getUserPermissionForGroup(group, session.getUserId());
         response.setPermission(currentNodePermissions);
 
-        response.setGroupsPanel(groupsTreeBuilder.listSubGroups(group.getId(), session.getUserId(), request));
+        response.setGroupsPanel(groupsTreeBuilder.listSubGroups(group, request, session.getUserId()));
 
         return response;
     }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/HomePageController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/HomePageController.java
index 6ad84e213d1cb5a7f2f7290285c29869f6be8f75..294ed8c1e77ee160d5123360456d36dea5950e61 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/HomePageController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/HomePageController.java
@@ -1,7 +1,7 @@
 package it.inaf.ia2.gms.controller;
 
 import it.inaf.ia2.gms.authn.SessionData;
-import it.inaf.ia2.gms.model.request.TabRequest;
+import it.inaf.ia2.gms.model.request.GroupsRequest;
 import it.inaf.ia2.gms.model.response.GroupsTabResponse;
 import it.inaf.ia2.gms.model.response.HomePageResponse;
 import javax.validation.Valid;
@@ -23,7 +23,7 @@ public class HomePageController {
 
     @ResponseBody
     @GetMapping(value = "/home", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
-    public ResponseEntity<HomePageResponse> getMainPage(@Valid TabRequest request) {
+    public ResponseEntity<HomePageResponse> getMainPage(@Valid GroupsRequest request) {
 
         HomePageResponse response = new HomePageResponse();
 
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/request/AddGroupRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/request/AddGroupRequest.java
index 3aeeb7fabef1fc4bc67b4a633722bc6b2576df91..b06f6b3074732d3a88f3409bfaf2c1515fff61e1 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/model/request/AddGroupRequest.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/request/AddGroupRequest.java
@@ -2,13 +2,15 @@ package it.inaf.ia2.gms.model.request;
 
 import javax.validation.constraints.NotEmpty;
 
-public class AddGroupRequest extends PaginatedModelRequest {
+public class AddGroupRequest extends PaginatedModelRequest implements SearchFilterRequest {
 
     @NotEmpty
     private String parentGroupId;
     @NotEmpty
     private String newGroupName;
 
+    private String searchFilter;
+
     public String getNewGroupName() {
         return newGroupName;
     }
@@ -24,4 +26,14 @@ public class AddGroupRequest extends PaginatedModelRequest {
     public void setParentGroupId(String parentGroupId) {
         this.parentGroupId = parentGroupId;
     }
+
+    @Override
+    public String getSearchFilter() {
+        return searchFilter;
+    }
+
+    @Override
+    public void setSearchFilter(String searchFilter) {
+        this.searchFilter = searchFilter;
+    }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/request/DeleteGroupRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/request/DeleteGroupRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3a2281a297d9fbfc5534a62a749a940d533060d8
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/request/DeleteGroupRequest.java
@@ -0,0 +1,16 @@
+package it.inaf.ia2.gms.model.request;
+
+public class DeleteGroupRequest extends PaginatedModelRequest implements SearchFilterRequest {
+
+    private String searchFilter;
+
+    @Override
+    public String getSearchFilter() {
+        return searchFilter;
+    }
+
+    @Override
+    public void setSearchFilter(String searchFilter) {
+        this.searchFilter = searchFilter;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/request/GroupsRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/request/GroupsRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe9d9a373d9e2da5048be6c95fb143b6390771cb
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/request/GroupsRequest.java
@@ -0,0 +1,30 @@
+package it.inaf.ia2.gms.model.request;
+
+public class GroupsRequest extends TabRequest implements SearchFilterRequest {
+
+    private String searchFilter;
+
+    /**
+     * Tells if returning only the groupsPanel or the complete GroupsTabResponse
+     * (containing also breadcrumbs and permission).
+     */
+    private boolean onlyPanel;
+
+    @Override
+    public String getSearchFilter() {
+        return searchFilter;
+    }
+
+    @Override
+    public void setSearchFilter(String searchFilter) {
+        this.searchFilter = searchFilter;
+    }
+
+    public boolean isOnlyPanel() {
+        return onlyPanel;
+    }
+
+    public void setOnlyPanel(boolean onlyPanel) {
+        this.onlyPanel = onlyPanel;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/request/RenameGroupRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/request/RenameGroupRequest.java
index 1112b444430724037c1254bed01e7e68191c0a2a..8137728bcf90404e450f748e177ec72f590fc56f 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/model/request/RenameGroupRequest.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/request/RenameGroupRequest.java
@@ -3,12 +3,14 @@ package it.inaf.ia2.gms.model.request;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
 
-public class RenameGroupRequest extends PaginatedModelRequest {
+public class RenameGroupRequest extends PaginatedModelRequest implements SearchFilterRequest {
 
     @NotNull
     @Size(min = 1)
     private String newGroupName;
 
+    private String searchFilter;
+
     public String getNewGroupName() {
         return newGroupName;
     }
@@ -16,4 +18,14 @@ public class RenameGroupRequest extends PaginatedModelRequest {
     public void setNewGroupName(String newGroupName) {
         this.newGroupName = newGroupName;
     }
+
+    @Override
+    public String getSearchFilter() {
+        return searchFilter;
+    }
+
+    @Override
+    public void setSearchFilter(String searchFilter) {
+        this.searchFilter = searchFilter;
+    }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/request/SearchFilterRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/request/SearchFilterRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e12de6ad3b28f013002ffb8c51ac329b31014a02
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/request/SearchFilterRequest.java
@@ -0,0 +1,8 @@
+package it.inaf.ia2.gms.model.request;
+
+public interface SearchFilterRequest {
+
+    String getSearchFilter();
+
+    void setSearchFilter(String searchFilter);
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
index 3469b41621b83ea0952197f2f7e50ea8acfaf856..da91f36e0fe1db77c12a0741a8f4b7639c80508b 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
@@ -124,16 +124,30 @@ public class GroupsDAO {
         });
     }
 
+    public List<GroupEntity> getDirectSubGroups(String path) {
+        return getDirectSubGroups(path, null);
+    }
+
     /**
      * Returns the direct children (one level).
      */
-    public List<GroupEntity> getDirectSubGroups(String path) {
+    public List<GroupEntity> getDirectSubGroups(String path, String searchFilter) {
 
-        String sql = "SELECT id, name, path FROM gms_group WHERE path ~ ? ORDER BY name";
+        boolean hasSearchFilter = searchFilter != null && !searchFilter.trim().isEmpty();
+
+        String sql;
+        if (hasSearchFilter) {
+            sql = "SELECT id, name, path FROM gms_group WHERE path ~ ? AND name ILIKE ? ORDER BY name";
+        } else {
+            sql = "SELECT id, name, path FROM gms_group WHERE path ~ ? ORDER BY name";
+        }
 
         return jdbcTemplate.query(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
             ps.setObject(1, getSubGroupsPath(path), Types.OTHER);
+            if (hasSearchFilter) {
+                ps.setObject(2, "%" + searchFilter + "%");
+            }
             return ps;
         }, resultSet -> {
             return getGroupsFromResultSet(resultSet);
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsTreeBuilder.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsTreeBuilder.java
index 665e90b471df90e8b8e08f9aae6b151e448f1d24..b56abf6f31f3e4ca3021de89f509716e6f0a0a3f 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsTreeBuilder.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsTreeBuilder.java
@@ -1,9 +1,9 @@
 package it.inaf.ia2.gms.service;
 
-import it.inaf.ia2.gms.exception.BadRequestException;
 import it.inaf.ia2.gms.model.GroupNode;
 import it.inaf.ia2.gms.model.response.PaginatedData;
 import it.inaf.ia2.gms.model.request.PaginatedModelRequest;
+import it.inaf.ia2.gms.model.request.SearchFilterRequest;
 import it.inaf.ia2.gms.persistence.GroupsDAO;
 import it.inaf.ia2.gms.persistence.PermissionsDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
@@ -28,20 +28,17 @@ public class GroupsTreeBuilder {
         this.permissionsDAO = permissionsDAO;
     }
 
-    public PaginatedData<GroupNode> listSubGroups(String parentGroupId, String userId, PaginatedModelRequest paginatedModelRequest) {
-
-        GroupEntity parent = groupsDAO.findGroupById(parentGroupId)
-                .orElseThrow(() -> new BadRequestException("Group " + parentGroupId + " not found"));
+    public <T extends PaginatedModelRequest & SearchFilterRequest> PaginatedData<GroupNode> listSubGroups(GroupEntity parentGroup, T request, String userId) {
 
         // All the sub groups of given parent
-        List<GroupEntity> allGroups = groupsDAO.getDirectSubGroups(parent.getPath());
+        List<GroupEntity> allGroups = groupsDAO.getDirectSubGroups(parentGroup.getPath(), request.getSearchFilter());
 
         // Select only the groups visible to the user
         List<PermissionEntity> permissions = permissionsDAO.findUserPermissions(userId);
         List<GroupNode> userGroups = filterOnPermissions(allGroups, permissions);
 
         PaginatedData<GroupNode> paginatedGroups = new PaginatedData<>(userGroups,
-                paginatedModelRequest.getPaginatorPage(), paginatedModelRequest.getPaginatorPageSize());
+                request.getPaginatorPage(), request.getPaginatorPageSize());
 
         fillHasChildrenFlags(paginatedGroups.getItems());
 
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilderTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilderTest.java
index baef55e7bea64ba63d5a0d8be5450b649d2b5393..07151d13506c5737cd5d658694314d492b75f90b 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilderTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilderTest.java
@@ -3,7 +3,7 @@ package it.inaf.ia2.gms.controller;
 import it.inaf.ia2.gms.authn.SessionData;
 import it.inaf.ia2.gms.model.GroupNode;
 import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.model.request.TabRequest;
+import it.inaf.ia2.gms.model.request.GroupsRequest;
 import it.inaf.ia2.gms.model.response.GroupsTabResponse;
 import it.inaf.ia2.gms.model.response.PaginatedData;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
@@ -56,9 +56,9 @@ public class GroupsTabResponseBuilderTest {
                 .thenReturn(Permission.ADMIN);
 
         PaginatedData<GroupNode> groupsPanel = new PaginatedData<>(new ArrayList<>(), 1, 10);
-        when(groupsTreeBuilder.listSubGroups(eq("ROOT"), eq("admin_id"), any())).thenReturn(groupsPanel);
+        when(groupsTreeBuilder.listSubGroups(eq(root), any(), eq("admin_id"))).thenReturn(groupsPanel);
 
-        TabRequest request = new TabRequest();
+        GroupsRequest request = new GroupsRequest();
         request.setGroupId("ROOT");
         request.setPaginatorPage(1);
         request.setPaginatorPageSize(10);
diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
index 2ca372b54103f2b7b57a51ac78df9390fff08d61..9d9fb6e051b43ad4c12782ab39a534186f816732 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
@@ -3,8 +3,8 @@ package it.inaf.ia2.gms.persistence;
 import it.inaf.ia2.gms.DataSourceConfig;
 import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.model.GroupNode;
-import it.inaf.ia2.gms.model.request.PaginatedModelRequest;
 import it.inaf.ia2.gms.model.Permission;
+import it.inaf.ia2.gms.model.request.GroupsRequest;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.persistence.model.PermissionEntity;
 import it.inaf.ia2.gms.service.GroupsTreeBuilder;
@@ -54,12 +54,12 @@ public class NestedGroupsIntegrationTest {
         GroupEntity lbtInafProgram1 = groupsService.addGroup(lbtInaf, "P1");
         GroupEntity lbtInafProgram2 = groupsService.addGroup(lbtInaf, "P2");
 
-        PaginatedModelRequest request = new PaginatedModelRequest();
+        GroupsRequest request = new GroupsRequest();
         request.setPaginatorPage(1);
         request.setPaginatorPageSize(50);
 
         // Test super admin - level 0 (ROOT)
-        List<GroupNode> groupNodes = groupNodes = groupsTreeBuilder.listSubGroups(root.getId(), userId, request).getItems();
+        List<GroupNode> groupNodes = groupNodes = groupsTreeBuilder.listSubGroups(root, request, userId).getItems();
         assertEquals(3, groupNodes.size());
         GroupNode lbtGN = groupNodes.get(0);
         assertEquals("LBT", lbtGN.getGroupName());
@@ -75,7 +75,7 @@ public class NestedGroupsIntegrationTest {
         assertFalse(tngGN.isHasChildren());
 
         // Test super admin - level 1
-        groupNodes = groupsTreeBuilder.listSubGroups(lbt.getId(), userId, request).getItems();
+        groupNodes = groupsTreeBuilder.listSubGroups(lbt, request, userId).getItems();
         assertEquals(1, groupNodes.size());
         GroupNode INAFGN = groupNodes.get(0);
         assertEquals("INAF", INAFGN.getGroupName());
@@ -83,7 +83,7 @@ public class NestedGroupsIntegrationTest {
         assertTrue(INAFGN.isHasChildren());
 
         // Test super admin - level 2
-        groupNodes = groupsTreeBuilder.listSubGroups(lbtInaf.getId(), userId, request).getItems();
+        groupNodes = groupsTreeBuilder.listSubGroups(lbtInaf, request, userId).getItems();
         assertEquals(2, groupNodes.size());
         GroupNode p1 = groupNodes.get(0);
         assertEquals("P1", p1.getGroupName());
@@ -119,7 +119,7 @@ public class NestedGroupsIntegrationTest {
         permissionsDAO.createOrUpdatePermission(radioPermission);
 
         // Check level 0 (ROOT)
-        groupNodes = groupsTreeBuilder.listSubGroups(root.getId(), userId, request).getItems();
+        groupNodes = groupsTreeBuilder.listSubGroups(root, request, userId).getItems();
         assertEquals(2, groupNodes.size());
         lbtGN = groupNodes.get(0);
         assertEquals("LBT", lbtGN.getGroupName());
@@ -131,7 +131,7 @@ public class NestedGroupsIntegrationTest {
         assertFalse(radioGN.isHasChildren());
 
         // Check level 1
-        groupNodes = groupsTreeBuilder.listSubGroups(lbt.getId(), userId, request).getItems();
+        groupNodes = groupsTreeBuilder.listSubGroups(lbt, request, userId).getItems();
         assertEquals(1, groupNodes.size());
         INAFGN = groupNodes.get(0);
         assertEquals("INAF", INAFGN.getGroupName());
@@ -139,7 +139,7 @@ public class NestedGroupsIntegrationTest {
         assertTrue(INAFGN.isHasChildren());
 
         // Check level 2
-        groupNodes = groupsTreeBuilder.listSubGroups(lbtInaf.getId(), userId, request).getItems();
+        groupNodes = groupsTreeBuilder.listSubGroups(lbtInaf, request, userId).getItems();
         assertEquals(2, groupNodes.size());
         p1 = groupNodes.get(0);
         assertEquals("P1", p1.getGroupName());