public Collection<Group> getGroups(int startIndex, int numResults) {
        HashSet<String> groupNames = getPagedGroupNamesFromCache(startIndex, numResults);
        if (groupNames == null) {
            // synchronizing on interned string isn't great, but this value is deemed sufficiently unique for this to be safe here.
            synchronized (pagedGroupNameKeyInterner.intern(getPagedGroupNameKey(startIndex, numResults))) {
                groupNames = getPagedGroupNamesFromCache(startIndex, numResults);
                if (groupNames == null) {
                    groupNames = new HashSet<>(provider.getGroupNames(startIndex, numResults));
                    savePagedGroupNamesFromCache(groupNames, startIndex, numResults);
                }
            }
        }
        return new GroupCollection(groupNames);
    }
Returns all groups given a start index and desired number of results. This is
useful to support pagination in a GUI where you may only want to display a certain
number of results per page. It is possible that the number of results returned will
be less than that specified by numResults if numResults is greater than the number
of records left in the system to display.

@param startIndex start index in results.
@param numResults number of results to return.
@return an Iterator for all groups in the specified range.
xmppserver/src/main/java/org/jivesoftware/openfire/group/GroupManager.java