diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java
index a3d57a020a5a298e852021d9628a4a9a74d78055..95fe2b1b843475f2178225c7900ab477272b2b0d 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java
@@ -110,31 +110,31 @@ public class JWTWebServiceController {
     @GetMapping(value = {"/ws/jwt/search/**", "/vo/search/**"}, produces = MediaType.TEXT_PLAIN_VALUE)
     public void isMemberOf(HttpServletRequest request, HttpServletResponse response) throws IOException {
 
-        String group = getGroupFromRequest(request, "/ws/jwt/search/", "/vo/search/");
-
-        List<String> groupNames = groupNameService.extractGroupNames(group);
-
-        boolean isMember = membershipManager.isCurrentUserMemberOf("ROOT");
-        if (!isMember) {
-            String parentPath = ""; // starting from ROOT
-            for (String groupName : groupNames) {
-                Optional<GroupEntity> optionalGroup = groupsDAO.findGroupByParentAndName(parentPath, groupName);
-                if (optionalGroup.isPresent()) {
-                    GroupEntity groupEntity = optionalGroup.get();
-                    parentPath = groupEntity.getPath();
-                    isMember = membershipManager.isCurrentUserMemberOf(groupEntity.getId());
-                    if (isMember) {
-                        break;
-                    }
-                } else {
-                    break;
+        String groupNamesString = getGroupFromRequest(request, "/ws/jwt/search/", "/vo/search/");
+
+        List<String> groupNames = groupNameService.extractGroupNames(groupNamesString);
+
+        GroupEntity group = null;
+
+        String parentPath = ""; // starting from ROOT
+        for (String groupName : groupNames) {
+            Optional<GroupEntity> optionalGroup = groupsDAO.findGroupByParentAndName(parentPath, groupName);
+            if (optionalGroup.isPresent()) {
+                GroupEntity groupEntity = optionalGroup.get();
+                parentPath = groupEntity.getPath();
+                boolean isMember = membershipManager.isCurrentUserMemberOf(groupEntity.getId());
+                if (isMember) {
+                    group = groupEntity;
                 }
+            } else {
+                group = null;
+                break;
             }
         }
 
-        if (isMember) {
+        if (group != null) {
             try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
-                pw.println(group);
+                pw.println(groupNameService.getCompleteName(groupNames));
             }
         }
         // else: empty response (as defined by GMS standard)
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipsDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipsDAO.java
index 2a4309d7f53f616e93ae895da6fa6c89bd5325fe..06476e98d944b291ff965b3ea647af3e7f706e95 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipsDAO.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipsDAO.java
@@ -120,18 +120,22 @@ public class MembershipsDAO {
 
     public boolean isMemberOf(String userId, String groupId) {
 
-        String sql = "SELECT COUNT(*) FROM gms_membership "
-                + " WHERE user_id = ? AND group_id = ?";
+        String sql = "SELECT COUNT(*)\n"
+                + "FROM gms_membership m\n"
+                + "JOIN gms_group g ON g.id = m.group_id\n"
+                + "JOIN gms_group gs ON g.path @> gs.path\n"
+                + "WHERE gs.id = ?\n"
+                + "AND m.user_id = ?";
 
         return jdbcTemplate.query(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
-            ps.setString(1, userId);
-            ps.setString(2, groupId);
+            ps.setString(1, groupId);
+            ps.setString(2, userId);
             return ps;
         }, resultSet -> {
             resultSet.next();
             int count = resultSet.getInt(1);
-            return count == 1;
+            return count > 0;
         });
     }
 
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupNameService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupNameService.java
index f6651e0f4f730b2a5b22d6a823152559a227daa5..8b58646149e0038c886b53d67d938d7b3cec0040 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupNameService.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupNameService.java
@@ -157,6 +157,12 @@ public class GroupNameService {
         return names;
     }
 
+    public String getCompleteName(List<String> names) {
+        return String.join(".", names.stream()
+                .map(n -> n.replace(".", "\\."))
+                .collect(Collectors.toList()));
+    }
+
     private GroupEntity getRoot() {
         return groupsDAO.findGroupById("ROOT")
                 .orElseThrow(() -> new IllegalStateException("Missing root group"));
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java
index 952f33d4a6859021ab6a46331455cb50ccb446ad..2f4b2a165d6af91ae1f469b661521c8552b749a6 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java
@@ -115,11 +115,15 @@ public class JWTWebServiceControllerTest {
 
         when(membershipManager.isCurrentUserMemberOf(eq(group3.getId()))).thenReturn(true);
 
-        String group = "group\\.1.subgroup.subsubgroup";
+        String group = "group%5C.1.subgroup.subsubgroup";
 
-        mockMvc.perform(get("/ws/jwt/search/" + group).principal(principal))
+        mockMvc.perform(get("/vo/search/" + group).principal(principal))
                 .andExpect(status().isOk())
-                .andExpect(content().string(group + "\n"));
+                .andExpect(content().string("group\\.1.subgroup.subsubgroup\n"));
+
+        mockMvc.perform(get("/vo/search/" + group + "/inexistent").principal(principal))
+                .andExpect(status().isOk())
+                .andExpect(content().string(""));
     }
 
     @Test
diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/MembershipsDAOTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/MembershipsDAOTest.java
index cb32b194eec76987f1bc9df058b0205bf7167a57..4529f4996aa89d9826334391263107c4b96f4c0f 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/persistence/MembershipsDAOTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/MembershipsDAOTest.java
@@ -37,8 +37,8 @@ public class MembershipsDAOTest {
 
     @Test
     public void testAddAndRemoveMembers() {
-        groupsDAO.createGroup(groupEntity("A"));
-        groupsDAO.createGroup(groupEntity("B"));
+        groupsDAO.createGroup(groupEntity("A", "A"));
+        groupsDAO.createGroup(groupEntity("B", "B"));
 
         assertTrue(membershipsDAO.findByGroup("A").isEmpty());
 
@@ -72,11 +72,48 @@ public class MembershipsDAOTest {
         membershipsDAO.deleteAllGroupsMembership(new ArrayList<>());
     }
 
-    private GroupEntity groupEntity(String groupId) {
+    @Test
+    public void testIsMemberOfRecursive() {
+
+        groupsDAO.createGroup(groupEntity("ROOT", ""));
+
+        groupsDAO.createGroup(groupEntity("A", "A"));
+        groupsDAO.createGroup(groupEntity("E", "A.E"));
+        groupsDAO.createGroup(groupEntity("F", "A.E.F"));
+
+        groupsDAO.createGroup(groupEntity("B", "B"));
+        groupsDAO.createGroup(groupEntity("C", "B.C"));
+        groupsDAO.createGroup(groupEntity("D", "B.C.D"));
+
+        membershipsDAO.addMember(membershipEntity(USER_1, "F"));
+        membershipsDAO.addMember(membershipEntity(USER_1, "C"));
+
+        assertFalse(membershipsDAO.isMemberOf(USER_1, "ROOT"));
+        assertFalse(membershipsDAO.isMemberOf(USER_1, "A"));
+        assertFalse(membershipsDAO.isMemberOf(USER_1, "B"));
+        assertTrue(membershipsDAO.isMemberOf(USER_1, "C"));
+        assertTrue(membershipsDAO.isMemberOf(USER_1, "D"));
+        assertFalse(membershipsDAO.isMemberOf(USER_1, "E"));
+        assertTrue(membershipsDAO.isMemberOf(USER_1, "F"));
+
+        membershipsDAO.addMember(membershipEntity(USER_1, "ROOT"));
+
+        assertTrue(membershipsDAO.isMemberOf(USER_1, "ROOT"));
+        assertTrue(membershipsDAO.isMemberOf(USER_1, "A"));
+        assertTrue(membershipsDAO.isMemberOf(USER_1, "B"));
+        assertTrue(membershipsDAO.isMemberOf(USER_1, "C"));
+        assertTrue(membershipsDAO.isMemberOf(USER_1, "D"));
+        assertTrue(membershipsDAO.isMemberOf(USER_1, "E"));
+        assertTrue(membershipsDAO.isMemberOf(USER_1, "F"));
+
+        assertFalse(membershipsDAO.isMemberOf(USER_1, "inexistent-id"));
+    }
+
+    private GroupEntity groupEntity(String groupId, String groupPath) {
         GroupEntity groupEntity = new GroupEntity();
         groupEntity.setId(groupId);
         groupEntity.setName(groupId);
-        groupEntity.setPath(groupId);
+        groupEntity.setPath(groupPath);
         groupEntity.setLeaf(false);
         return groupEntity;
     }