private Collection<String> getUsernames(int startIndex, int numResults) {
        List<String> usernames = new ArrayList<>(500);
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = getConnection();
            if ((startIndex==0) && (numResults==Integer.MAX_VALUE))
            {
                // MSSQL differentiates between client-cursored and server-cursored result sets. For server-cursored result
                // sets, the fetch buffer and scroll window are the same size (as opposed to fetch buffer containing all
                // the rows). To hint that a server-cursored result set is desired, it should be configured to be 'forward
                // only' as well as 'read only'.
                pstmt = con.prepareStatement(allUsersSQL, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                // Set the fetch size. This will prevent some JDBC drivers from trying
                // to load the entire result set into memory.
                DbConnectionManager.setFetchSize(pstmt, 500);
                pstmt.setFetchDirection(ResultSet.FETCH_FORWARD);
                rs = pstmt.executeQuery();
                while (rs.next()) {
                    // OF-1837: When the database does not hold escaped data, escape values before processing them further.
                    final String username;
                    if (assumePersistedDataIsEscaped()) {
                        username = rs.getString(1);
                    } else {
                        username = JID.escapeNode( rs.getString(1) );
                    }
                    usernames.add(username);
                }
            }
            else {
                pstmt = DbConnectionManager.createScrollablePreparedStatement(con, allUsersSQL);
                DbConnectionManager.limitRowsAndFetchSize(pstmt, startIndex, numResults);
                rs = pstmt.executeQuery();
                DbConnectionManager.scrollResultSet(rs, startIndex);
                int count = 0;
                while (rs.next() && count < numResults) {
                    // OF-1837: When the database does not hold escaped data, escape values before processing them further.
                    final String username;
                    if (assumePersistedDataIsEscaped()) {
                        username = rs.getString(1);
                    } else {
                        username = JID.escapeNode( rs.getString(1) );
                    }
                    usernames.add(username);
                    count++;
                }
            }
            if (Log.isDebugEnabled()) {
                   Log.debug("Results: " + usernames.size());
                   LogResults(usernames);
            }
        }
        catch (SQLException e) {
            Log.error(e.getMessage(), e);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }
        return usernames;
    }
XMPP disallows some characters in identifiers, requiring them to be escaped.

This implementation assumes that the database returns properly escaped identifiers,
but can apply escaping by setting the value of the 'jdbcUserProvider.isEscaped'
property to 'false'.

@return 'false' if this implementation needs to escape database content before processing.
xmppserver/src/main/java/org/jivesoftware/openfire/user/JDBCUserProvider.java