diff --git a/gms-ui/package-lock.json b/gms-ui/package-lock.json
index f41f0cbc8b890d9a1afcf665f64a366ceeba8c3c..e988bbac737fe4b8cd3ca5ae0aa49607569bfbe9 100644
--- a/gms-ui/package-lock.json
+++ b/gms-ui/package-lock.json
@@ -2367,13 +2367,12 @@
       "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag=="
     },
     "bootstrap-vue": {
-      "version": "2.0.0-rc.24",
-      "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.24.tgz",
-      "integrity": "sha512-8rA/I9tOvpNVIuMKD3rdlrUqgVdPEw4vPI0X8OeFJcG2hHvCHeZDF7FmWqxSeehIrUHGDV17HlTGSuP/v1Sp5g==",
+      "version": "2.0.0-rc.27",
+      "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.27.tgz",
+      "integrity": "sha512-gXdpt2IsKbmC3SU0bf/RgldWwgrXK7G47yslOtkk4OA1z6fOzb2orM2vU5L8NCNZQomYax9LapRucv+8PByfdA==",
       "requires": {
         "@nuxt/opencollective": "^0.2.2",
         "bootstrap": "^4.3.1",
-        "core-js": ">=2.6.5 <3.0.0",
         "popper.js": "^1.15.0",
         "portal-vue": "^2.1.5",
         "vue-functional-data-merge": "^3.1.0"
diff --git a/gms-ui/package.json b/gms-ui/package.json
index f963d0c13fedab73af4b738b028ce644e54d8418..2b6cd4b52fb816a543ea4bd377af3de87a79cadb 100644
--- a/gms-ui/package.json
+++ b/gms-ui/package.json
@@ -11,7 +11,7 @@
     "@fortawesome/fontawesome-svg-core": "^1.2.19",
     "@fortawesome/free-solid-svg-icons": "^5.9.0",
     "@fortawesome/vue-fontawesome": "^0.1.6",
-    "bootstrap-vue": "^2.0.0-rc.19",
+    "bootstrap-vue": "^2.0.0-rc.27",
     "core-js": "^2.6.5",
     "vue": "^2.6.10",
     "vuex": "^3.1.1"
diff --git a/gms-ui/src/App.vue b/gms-ui/src/App.vue
index c5635dbef5351ad33721a430cecb0fdec2c49c73..ed823404a633fc965f37c41d34c5a7d70b9994b2 100644
--- a/gms-ui/src/App.vue
+++ b/gms-ui/src/App.vue
@@ -11,6 +11,7 @@
 import TopMenu from './components/TopMenu.vue';
 import Main from './components/Main.vue';
 import { mapState } from 'vuex';
+//import { ToastPlugin } from 'bootstrap-vue';
 
 export default {
   name: 'app',
@@ -23,6 +24,15 @@ export default {
   }),
   mounted: function() {
     this.$store.commit('fetchGroupsModel');
+
+    var self = this;
+    document.addEventListener('apiError', function (event) {
+      self.$bvToast.toast(event.message, {
+        title: "Error",
+        variant: 'danger',
+        solid: true
+      });
+    })
   }
 }
 </script>
diff --git a/gms-ui/src/api/server/index.js b/gms-ui/src/api/server/index.js
index 1fc56867c964a2cb00b49a2da4ffb2ea37e995c4..00c8879d5034b84f0cad4c26675ed209efb57f49 100644
--- a/gms-ui/src/api/server/index.js
+++ b/gms-ui/src/api/server/index.js
@@ -1,9 +1,43 @@
 const BASE_API_URL = "http://localhost:8081/"
 
+function apiRequest(url, data) {
+  return new Promise((resolve, reject) => {
+    fetch(url, data)
+      .then(response => {
+        if([200, 201, 204, 400].includes(response.status)) { // valid status codes
+          resolve(response.json());
+        } else {
+          response.json().then(jsonValue => dispatchApiErrorEvent(jsonValue));
+        }
+      })
+      .catch(error => {
+        dispatchApiErrorEvent(error);
+      });
+  });
+}
+
+function dispatchApiErrorEvent(error) {
+  let message;
+  if(error.message) {
+    message = error.message;
+  } else {
+    message = 'Generic error';
+  }
+
+  let event = new CustomEvent('apiError');
+  event.message = message;
+  document.dispatchEvent(event);
+}
+
 export default {
   fetchGroupsModel (input) {
-    let url = BASE_API_URL + 'groups?groupId=' + input.selectedGroupId + '&tab=' + input.selectedTab + '&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPage=' + input.paginatorPage;
-    return fetch(url, {
+    let url = BASE_API_URL
+            + 'groups?groupId=' + input.selectedGroupId
+            + '&tab=' + input.selectedTab
+            + '&paginatorPageSize=' + input.paginatorPageSize
+            + '&paginatorPage=' + input.paginatorPage
+            + '&page=' + input.page;
+    return apiRequest(url, {
       method: 'GET',
       cache: 'no-cache',
       credentials: 'include',
@@ -11,6 +45,55 @@ export default {
         'Content-Type': 'application/json',
         'Accept': 'application/json',
       }
-    }).then(response => response.json());
+    });
+  },
+  addGroup (newGroupName, input) {
+    let url = BASE_API_URL + 'group';
+    return apiRequest(url, {
+      method: 'POST',
+      cache: 'no-cache',
+      credentials: 'include',
+      headers: {
+        'Content-Type': 'application/json',
+        'Accept': 'application/json',
+      },
+      body: JSON.stringify({
+        newGroupName: newGroupName,
+        parentGroupId: input.selectedGroupId,
+        paginatorPageSize: input.paginatorPageSize,
+        paginatorPage: input.paginatorPage
+      })
+    });
+  },
+  renameGroup (groupId, newGroupName, input) {
+    let url = BASE_API_URL + 'group/' + groupId;
+    return apiRequest(url, {
+      method: 'PUT',
+      cache: 'no-cache',
+      credentials: 'include',
+      headers: {
+        'Content-Type': 'application/json',
+        'Accept': 'application/json',
+      },
+      body: JSON.stringify({
+        newGroupName: newGroupName,
+        paginatorPageSize: input.paginatorPageSize,
+        paginatorPage: input.paginatorPage
+      })
+    });
+  },
+  deleteGroup (groupId, input) {
+    let url = BASE_API_URL + 'group/' + groupId
+            + '?paginatorPageSize=' + input.paginatorPageSize
+            + '&paginatorPage=' + input.paginatorPage;
+    return apiRequest(url, {
+      method: 'DELETE',
+      cache: 'no-cache',
+      credentials: 'include',
+      headers: {
+        'Content-Type': 'application/json',
+        'Accept': 'application/json',
+      }
+    });
   }
 };
diff --git a/gms-ui/src/components/GroupsPanel.vue b/gms-ui/src/components/GroupsPanel.vue
index 38fc4bcedb6eec49e1884f0a16a5e01c252ea6b9..bc37ed96aa7651c79370caafd5402b36cbec738b 100644
--- a/gms-ui/src/components/GroupsPanel.vue
+++ b/gms-ui/src/components/GroupsPanel.vue
@@ -8,12 +8,13 @@
     <div id="groups-list">
       <b-list-group v-for="group in model.groupsPanel.items">
         <b-list-group-item href="#" v-on:click="openGroup(group)">
-          <span class="float-left">{{group.name}}</span>
+          <span class="float-left">{{group.groupName}}</span>
           <span v-if="group.permissions.includes('ADMIN')" class="float-right">
-            <a href="#" v-on:click="renameGroup(group)">
+            <a href="#" v-on:click.stop="openRenameGroupModal(group)" title="Rename">
               <font-awesome-icon icon="edit"></font-awesome-icon>
             </a>
-            <a href="#" v-on:click="deleteGroup(group)">
+            &nbsp;
+            <a href="#" v-on:click.stop="openDeleteGroupModal(group)" class="text-danger" title="Delete">
               <font-awesome-icon icon="trash"></font-awesome-icon>
             </a>
           </span>
@@ -46,14 +47,22 @@
     </div>
     <div class="text-center">
     </div>
+    <RenameGroupModal ref="renameGroupModal" />
+    <ConfirmDeleteGroupModal ref="confirmDeleteGroupModal" />
   </b-tab>
 </template>
 
 <script>
+import RenameGroupModal from './modals/RenameGroupModal.vue'
+import ConfirmDeleteGroupModal from './modals/ConfirmDeleteGroupModal.vue'
 import { mapState, mapActions } from 'vuex'
 
 export default {
   name: 'GroupsPanel',
+  components: {
+    RenameGroupModal,
+    ConfirmDeleteGroupModal
+  },
   computed: mapState({
     model: state => state.model,
     input: state => state.input
@@ -70,14 +79,14 @@ export default {
   },
   methods: {
     openGroup: function(group) {
-      this.$store.state.input.selectedGroupId = group.id;
+      this.$store.state.input.selectedGroupId = group.groupId;
       this.$store.commit('fetchGroupsModel');
     },
-    renameGroup: function(group) {
-      console.log('rename ' + group.id);
+    openRenameGroupModal: function(group) {
+      this.$refs.renameGroupModal.openRenameGroupModal(group);
     },
-    deleteGroup: function(group) {
-      console.log('del ' + group.id);
+    openDeleteGroupModal: function(group) {
+      this.$refs.confirmDeleteGroupModal.openDeleteGroupModal(group);
     },
     setPage: function(page) {
       console.log('setPage ', page);
diff --git a/gms-ui/src/components/Main.vue b/gms-ui/src/components/Main.vue
index 95aa9fb5532ed423159b01a2c419f2c421d783a3..87442ad1228f0d3d898baf363eea45048cb1888c 100644
--- a/gms-ui/src/components/Main.vue
+++ b/gms-ui/src/components/Main.vue
@@ -3,14 +3,14 @@
     <GroupsBreadcrumb />
     <div class="">
       <button type="button" class="btn btn-primary float-right" v-if="model.permissions.includes('ADMIN')">Add member</button>
-      <button type="button" class="btn btn-primary float-right" v-if="model.permissions.includes('ADMIN')">Add group</button>
-      <button type="button" class="btn btn-primary float-right" v-if="model.permissions.includes('ADMIN') || model.permissions.includes('MANAGE_MEMBERS')">Add collaborator</button>
+      <b-button variant="primary" class="float-right" v-if="model.permissions.includes('ADMIN')" v-b-modal.new-group-modal>Add group</b-button>
+      <button type="button" class="btn btn-primary float-right" v-if="model.permissions.includes('MANAGE_MEMBERS')">Add collaborator</button>
     </div>
     <b-tabs content-class="mt-3">
       <GroupsPanel />
       <MembersPanel />
-      </b-tab>
     </b-tabs>
+    <NewGroupModal />
   </div>
 </template>
 
@@ -18,6 +18,7 @@
 import GroupsBreadcrumb from './GroupsBreadcrumb.vue'
 import GroupsPanel from './GroupsPanel.vue'
 import MembersPanel from './MembersPanel.vue'
+import NewGroupModal from './modals/NewGroupModal.vue'
 import { mapState } from 'vuex';
 
 export default {
@@ -25,16 +26,14 @@ export default {
   components: {
     GroupsBreadcrumb,
     GroupsPanel,
-    MembersPanel
+    MembersPanel,
+    NewGroupModal
   },
   computed: mapState({
     model: state => state.model
   }),
   methods: {
-    addGroup: function() {
-
-    },
-    addMemeber: function() {
+    addMember: function() {
 
     }
   }
diff --git a/gms-ui/src/components/modals/ConfirmDeleteGroupModal.vue b/gms-ui/src/components/modals/ConfirmDeleteGroupModal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..05d21834772896d0e468b3dddcbc0652f96994d9
--- /dev/null
+++ b/gms-ui/src/components/modals/ConfirmDeleteGroupModal.vue
@@ -0,0 +1,33 @@
+<template>
+  <b-modal id="confirm-delete-group-modal" title="Confirm action" ok-title="Delete" @ok="deleteGroup" ok-variant="danger">
+    <b-form inline>
+      <p v-if="groupToDelete">Are you sure that you want to delete the group <strong>{{groupToDelete.groupName}}</strong>?</p>
+    </b-form>
+  </b-modal>
+</template>
+
+<script>
+import client from 'api-client';
+
+export default {
+  name: 'ConfirmDeleteGroupModal',
+  data: function() {
+    return {
+      groupToDelete: null
+    }
+  },
+  methods: {
+    openDeleteGroupModal: function(group) {
+      this.groupToDelete = group;
+      this.$bvModal.show('confirm-delete-group-modal');
+    },
+    deleteGroup: function() {
+      client.deleteGroup(this.groupToDelete.groupId, this.$store.state.input)
+        .then(res => {
+          this.$store.commit('updateGroupsPanel', res);
+          this.$bvModal.hide('confirm-delete-group-modal');
+        })
+    }
+  }
+}
+</script>
diff --git a/gms-ui/src/components/modals/NewGroupModal.vue b/gms-ui/src/components/modals/NewGroupModal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e976b8c905986de3fc529b295615ab1e36e294fa
--- /dev/null
+++ b/gms-ui/src/components/modals/NewGroupModal.vue
@@ -0,0 +1,65 @@
+<template>
+  <b-modal id="new-group-modal" title="Add group" @show="resetModal" ok-title="Add" @ok="addGroup">
+    <b-form inline>
+      <label class="w-25" for="new-group-name-input">Group name:</label>
+      <b-form-input v-model="newGroupName"
+                    id="new-group-name-input"
+                    class="w-75"
+                    aria-describedby="new-group-name-input-feedback"
+                   :state="newGroupNameState"
+                    v-on:input="resetError">
+      </b-form-input>
+      <b-form-invalid-feedback id="new-group-name-input-feedback">{{newGroupNameError}}</b-form-invalid-feedback>
+    </b-form>
+  </b-modal>
+</template>
+
+<script>
+import client from 'api-client';
+
+export default {
+  name: 'NewGroupModal',
+  computed: {
+    newGroupNameState() {
+      if(this.newGroupNameError) {
+        return 'invalid';
+      }
+    }
+  },
+  data: function() {
+    return {
+      newGroupName: null,
+      newGroupNameError: null
+    };
+  },
+  methods: {
+    resetModal: function() {
+      this.newGroupName = null;
+      this.resetError();
+    },
+    resetError: function() {
+      this.newGroupNameError = null;
+    },
+    addGroup: function(event) {
+      // Prevent modal from closing
+      event.preventDefault();
+
+      if(!this.newGroupName) {
+        this.newGroupNameError = "Group name is required";
+        return;
+      }
+
+      let parent = this.$store.getters.selectedGroupId;
+      client.addGroup(this.newGroupName, this.$store.state.input)
+        .then(res => {
+          if(res.status === 400) {
+            this.newGroupNameError = res.message;
+          } else {
+            this.$store.commit('updateGroupsPanel', res);
+            this.$bvModal.hide('new-group-modal');
+          }
+        });
+    }
+  }
+}
+</script>
diff --git a/gms-ui/src/components/modals/RenameGroupModal.vue b/gms-ui/src/components/modals/RenameGroupModal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..42af1b98471b28b53ea4546354f8b9b0741af3ef
--- /dev/null
+++ b/gms-ui/src/components/modals/RenameGroupModal.vue
@@ -0,0 +1,73 @@
+<template>
+  <b-modal id="rename-group-modal" title="Rename group" ok-title="Rename" @ok="renameGroup">
+    <b-form inline>
+      <label class="w-25" for="new-group-name-input">Group name:</label>
+      <b-form-input v-model="newGroupName"
+                    id="new-group-name-input"
+                    class="w-75"
+                    aria-describedby="new-group-name-input-feedback"
+                   :state="newGroupNameState"
+                    v-on:input="resetError">
+      </b-form-input>
+      <b-form-invalid-feedback id="new-group-name-input-feedback">{{newGroupNameError}}</b-form-invalid-feedback>
+    </b-form>
+  </b-modal>
+</template>
+
+<script>
+import client from 'api-client';
+
+export default {
+  name: 'RenameGroupModal',
+  computed: {
+    newGroupNameState() {
+      if(this.newGroupNameError) {
+        return 'invalid';
+      }
+    }
+  },
+  data: function() {
+    return {
+      groupId: null,
+      oldGroupName: null,
+      newGroupName: null,
+      newGroupNameError: null
+    };
+  },
+  methods: {
+    resetError: function() {
+      this.newGroupNameError = null;
+    },
+    openRenameGroupModal: function(group) {
+      this.newGroupName = group.groupName;
+      this.groupId = group.groupId;
+      this.$bvModal.show('rename-group-modal');
+    },
+    renameGroup: function(event) {
+      // Prevent modal from closing
+      event.preventDefault();
+
+      if(!this.newGroupName) {
+        this.newGroupNameError = "Group name is required";
+        return;
+      }
+
+      if(this.oldGroupName === this.newGroupName) {
+        this.$bvModal.hide('rename-group-modal');
+        return;
+      }
+
+      let parent = this.$store.getters.selectedGroupId;
+      client.renameGroup(this.groupId, this.newGroupName, this.$store.state.input)
+        .then(res => {
+          if(res.status === 400) {
+            this.newGroupNameError = res.message;
+          } else {
+            this.$store.commit('updateGroupsPanel', res);
+            this.$bvModal.hide('rename-group-modal');
+          }
+        });
+    }
+  }
+}
+</script>
diff --git a/gms-ui/src/plugins/bootstrap-vue.js b/gms-ui/src/plugins/bootstrap-vue.js
index 7ad0b7c63b50dd355c0b04584f71b039cf5bdd25..c29a066a30dc2d0588c297632514a86edc1989cd 100644
--- a/gms-ui/src/plugins/bootstrap-vue.js
+++ b/gms-ui/src/plugins/bootstrap-vue.js
@@ -1,7 +1,8 @@
-import Vue from 'vue'
+import Vue from 'vue';
 
-import BootstrapVue from 'bootstrap-vue'
-import 'bootstrap/dist/css/bootstrap.min.css'
-import 'bootstrap-vue/dist/bootstrap-vue.css'
+import { BootstrapVue } from 'bootstrap-vue';
+import 'bootstrap/dist/css/bootstrap.min.css';
+import 'bootstrap-vue/dist/bootstrap-vue.css';
 
-Vue.use(BootstrapVue)
+Vue.use(BootstrapVue);
+//Vue.use(ToastPlugin);
diff --git a/gms-ui/src/store.js b/gms-ui/src/store.js
index dcf9906887a82740bc935afdcf71d9575664a89a..c6e95938d4ec09717f28576cf62fcf007f60a5f2 100644
--- a/gms-ui/src/store.js
+++ b/gms-ui/src/store.js
@@ -2,7 +2,7 @@
 
 import Vue from 'vue';
 import Vuex from 'vuex';
-import client from 'api-client'
+import client from 'api-client';
 
 Vue.use(Vuex);
 
@@ -15,7 +15,8 @@ export default new Vuex.Store({
       selectedGroupId: 'ROOT',
       paginatorPageSize: 20,
       paginatorPage: 1,
-      selectedTab: 'groups'
+      selectedTab: 'groups',
+      page: 'main'
     }
   },
   mutations: {
@@ -24,6 +25,14 @@ export default new Vuex.Store({
         .then(model => {
           this.state.model = model;
         });
+    },
+    updateGroupsPanel(state, groupsPanel) {
+      this.state.model.groupsPanel = groupsPanel;
+    }
+  },
+  getters: {
+    selectedGroupId: state => {
+      return state.model.breadcrumbs[state.model.breadcrumbs.length - 1].groupId;
     }
   }
 });
diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java b/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java
index 42fa7388f43e7a51de285fcec97ba8925fe88f24..c1b4c6a54f6cf8b42b984e0f92cef3b5059b235f 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java
@@ -45,6 +45,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
     public FilterRegistrationBean corsFilter() {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
         CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
+        config.addAllowedMethod(HttpMethod.PUT);
+        config.addAllowedMethod(HttpMethod.DELETE);
         config.setAllowedOrigins(Arrays.asList(corsAllowedOrigin));
         config.setAllowCredentials(true);
         source.registerCorsConfiguration("/**", config);
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 4ee7683fd7b167f1ba269aa677aa5f8bce0cda05..256cc9a0604f9db95131615db0333f740ce2a188 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
@@ -1,21 +1,32 @@
 package it.inaf.ia2.gms.controller;
 
 import it.inaf.ia2.gms.authn.SessionData;
+import it.inaf.ia2.gms.exception.BadRequestException;
 import it.inaf.ia2.gms.model.CreateGroupRequest;
+import it.inaf.ia2.gms.model.GroupNode;
 import it.inaf.ia2.gms.model.GroupsModelRequest;
 import it.inaf.ia2.gms.model.GroupsModelResponse;
+import it.inaf.ia2.gms.model.PaginatedData;
+import it.inaf.ia2.gms.model.PaginatedModelRequest;
+import it.inaf.ia2.gms.model.RenameGroupRequest;
 import it.inaf.ia2.gms.persistence.model.Group;
 import it.inaf.ia2.gms.persistence.model.User;
 import it.inaf.ia2.gms.service.GroupsModelService;
 import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.service.UsersService;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import javax.validation.Valid;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -40,9 +51,44 @@ public class GroupsController {
     }
 
     @PostMapping(value = "/group", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
-    public ResponseEntity<String> createGroup(@Valid @RequestBody CreateGroupRequest createGroupRequest) {
-        User user = usersService.getUserById(session.getUserId());
-        Group group = groupsService.addGroup(createGroupRequest.getParentGroupId(), createGroupRequest.getGroupName(), user);
-        return new ResponseEntity<>(group.getId(), HttpStatus.CREATED);
+    public ResponseEntity<PaginatedData<GroupNode>> createGroup(@Valid @RequestBody CreateGroupRequest request) {
+
+        User user = getUser();
+        Group newGroup = groupsService.addGroup(request.getParentGroupId(), request.getNewGroupName(), user);
+
+        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(newGroup.getParentGroup(), request, user);
+
+        return new ResponseEntity<>(groupsPanel, HttpStatus.CREATED);
+    }
+
+    @PutMapping(value = "/group/{groupId}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
+    public ResponseEntity<PaginatedData<GroupNode>> renameGroup(@PathVariable("groupId") String groupId, @Valid @RequestBody RenameGroupRequest request) {
+
+        User user = getUser();
+        Group renamedGroup = groupsService.renameGroup(groupId, request.getNewGroupName(), user);
+
+        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(renamedGroup.getParentGroup(), request, user);
+
+        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) {
+
+        User user = getUser();
+        Group parent = groupsService.deleteGroup(groupId, user);
+
+        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent, request, user);
+
+        return ResponseEntity.ok(groupsPanel);
+    }
+
+    private User getUser() {
+        return usersService.getUserById(session.getUserId());
+    }
+
+    private PaginatedData<GroupNode> getGroupsPanel(Group parentGroup, PaginatedModelRequest paginatedRequest, User user) {
+        List<GroupNode> groupNodes = groupsService.getSubgroups(parentGroup, user);
+        return new PaginatedData<>(groupNodes, paginatedRequest.getPaginatorPage(), paginatedRequest.getPaginatorPageSize());
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/exception/BadRequestException.java b/gms/src/main/java/it/inaf/ia2/gms/exception/BadRequestException.java
index 039ecfe5de87742c49e5da4b04dbeb51342788ba..50f633f15518320125e89e2d79f58735b5c9195f 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/exception/BadRequestException.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/exception/BadRequestException.java
@@ -1,5 +1,9 @@
 package it.inaf.ia2.gms.exception;
 
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(value = HttpStatus.BAD_REQUEST)
 public class BadRequestException extends RuntimeException {
 
     public BadRequestException(String message) {
diff --git a/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java b/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java
index 2b07d8c30bb0f9c9f5df74611726acfc0f781dcc..e4f73db59e96d619a5cc6e8b8c0458347cd2ebb2 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java
@@ -1,5 +1,9 @@
 package it.inaf.ia2.gms.exception;
 
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
 public class UnauthorizedException extends RuntimeException {
 
     public UnauthorizedException(String message) {
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelRequest.java
deleted file mode 100644
index 1330e697373ab887171e3839102c2d9df31d2f72..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelRequest.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package it.inaf.ia2.gms.model;
-
-public abstract class BaseModelRequest {
-
-    private String page;
-
-    public String getPage() {
-        return page;
-    }
-
-    public void setPage(String page) {
-        this.page = page;
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java
index a62c852f9186227f7ebaa2a43abc3875eb3a8d2f..c7444a9667c480de21bd19dc7be602bb765060b7 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java
@@ -1,23 +1,23 @@
 package it.inaf.ia2.gms.model;
 
-public class CreateGroupRequest {
+public class CreateGroupRequest extends PaginatedModelRequest {
 
     private String parentGroupId;
-    private String groupName;
+    private String newGroupName;
 
-    public String getParentGroupId() {
-        return parentGroupId;
+    public String getNewGroupName() {
+        return newGroupName;
     }
 
-    public void setParentGroupId(String parentGroupId) {
-        this.parentGroupId = parentGroupId;
+    public void setNewGroupName(String newGroupName) {
+        this.newGroupName = newGroupName;
     }
 
-    public String getGroupName() {
-        return groupName;
+    public String getParentGroupId() {
+        return parentGroupId;
     }
 
-    public void setGroupName(String groupName) {
-        this.groupName = groupName;
+    public void setParentGroupId(String parentGroupId) {
+        this.parentGroupId = parentGroupId;
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java
index ccdaf286a6d995ac2ad29bba701abada20d95cee..6ca71a1e443eb6815c707eb81837e026d6b15e57 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java
@@ -1,6 +1,5 @@
 package it.inaf.ia2.gms.model;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -18,7 +17,6 @@ public class GroupNode {
         permissions = new HashSet<>();
     }
 
-    @JsonProperty("id")
     public String getGroupId() {
         return groupId;
     }
@@ -27,7 +25,6 @@ public class GroupNode {
         this.groupId = groupId;
     }
 
-    @JsonProperty("name")
     public String getGroupName() {
         return groupName;
     }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java
index 79e22f42680ad960288957a29d34b603c290b7d1..5c7a393ce19c8420069bc5b00f256b3f723bbb31 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java
@@ -1,9 +1,8 @@
 package it.inaf.ia2.gms.model;
 
-import javax.validation.constraints.Min;
 import javax.validation.constraints.NotNull;
 
-public class GroupsModelRequest extends BaseModelRequest {
+public class GroupsModelRequest extends PaginatedModelRequest {
 
     @NotNull
     private String groupId;
@@ -11,9 +10,8 @@ public class GroupsModelRequest extends BaseModelRequest {
     @NotNull
     private String tab;
 
-    @Min(1)
-    private int paginatorPage;
-    private int paginatorPageSize;
+    @NotNull
+    private String page;
 
     public String getGroupId() {
         return groupId;
@@ -31,19 +29,11 @@ public class GroupsModelRequest extends BaseModelRequest {
         this.tab = tab;
     }
 
-    public int getPaginatorPage() {
-        return paginatorPage;
-    }
-
-    public void setPaginatorPage(int paginatorPage) {
-        this.paginatorPage = paginatorPage;
-    }
-
-    public int getPaginatorPageSize() {
-        return paginatorPageSize;
+    public String getPage() {
+        return page;
     }
 
-    public void setPaginatorPageSize(int paginatorPageSize) {
-        this.paginatorPageSize = paginatorPageSize;
+    public void setPage(String page) {
+        this.page = page;
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/PaginatedModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/PaginatedModelRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..371b90b548ab9794e7851f18689b7f37d4e56717
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/PaginatedModelRequest.java
@@ -0,0 +1,26 @@
+package it.inaf.ia2.gms.model;
+
+import javax.validation.constraints.Min;
+
+public class PaginatedModelRequest {
+
+    @Min(1)
+    private int paginatorPage;
+    private int paginatorPageSize;
+
+    public int getPaginatorPage() {
+        return paginatorPage;
+    }
+
+    public void setPaginatorPage(int paginatorPage) {
+        this.paginatorPage = paginatorPage;
+    }
+
+    public int getPaginatorPageSize() {
+        return paginatorPageSize;
+    }
+
+    public void setPaginatorPageSize(int paginatorPageSize) {
+        this.paginatorPageSize = paginatorPageSize;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/RenameGroupRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/RenameGroupRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..90feff7ea368b6932de9f903140d74a18f30bc17
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/RenameGroupRequest.java
@@ -0,0 +1,19 @@
+package it.inaf.ia2.gms.model;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+public class RenameGroupRequest extends PaginatedModelRequest {
+
+    @NotNull
+    @Size(min = 1)
+    private String newGroupName;
+
+    public String getNewGroupName() {
+        return newGroupName;
+    }
+
+    public void setNewGroupName(String newGroupName) {
+        this.newGroupName = newGroupName;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
index 4be39d1f5dd4e8ebbd28aee5361cfc6b968684b3..c82324af529a173586df1a9a256acbf0cd842454 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
@@ -53,15 +53,15 @@ public class GroupsService {
 
         Group parent = getGroupById(parentId);
 
+        if (!getPermissions(parent, user).contains(Permission.ADMIN)) {
+            throw new UnauthorizedException("Missing admin privileges");
+        }
+
         if (parent.getChildrenGroups().stream()
                 .anyMatch(g -> g.getName().equals(groupName))) {
             throw new BadRequestException("There is already a group named " + groupName);
         }
 
-        if (!getPermissions(parent, user).contains(Permission.ADMIN)) {
-            throw new UnauthorizedException("Missing admin privileges");
-        }
-
         Group group = new Group();
         group.setId(UUID.randomUUID().toString());
         group.setName(groupName);
@@ -72,6 +72,42 @@ public class GroupsService {
         return group;
     }
 
+    public Group renameGroup(String groupId, String newGroupName, User user) {
+
+        Group group = getGroupById(groupId);
+
+        if (!getPermissions(group, user).contains(Permission.ADMIN)) {
+            throw new UnauthorizedException("Missing admin privileges");
+        }
+
+        if (group.getParentGroup().getChildrenGroups().stream()
+                .anyMatch(g -> g.getName().equals(newGroupName))) {
+            throw new BadRequestException("There is already a group named " + newGroupName);
+        }
+
+        group.setName(newGroupName);
+        return groupsRepository.save(group);
+    }
+
+    public Group deleteGroup(String groupId, User user) {
+
+        Group group = getGroupById(groupId);
+
+        if (!getPermissions(group, user).contains(Permission.ADMIN)) {
+            throw new UnauthorizedException("Missing admin privileges");
+        }
+
+        if (ROOT.equals(group.getId())) {
+            throw new UnauthorizedException("It is not possible to remove the ROOT");
+        }
+
+        Group parent = group.getParentGroup();
+        
+        groupsRepository.delete(group);
+        
+        return parent;
+    }
+
     @Transactional
     public List<GroupNode> getSubgroups(Group parent, User user) {