Round Robin Tournament
This is somehow the most obfuscated code that I seen on this year perhaps, but it's not that bad, at least not after spending some time to figure it out.
But that's why we are here, to make it better.

For this review I will be replacing Controller.random with new Random(1) for simplification.

public Round nextRound() {
    int playersRemaining = getPlayerCount();
    List&lt;Player&gt; nextPlayers = new ArrayList&lt;&gt;();
    while (playersRemaining &gt; 0) {
        playersRemaining = addPlayers(nextPlayers, playersRemaining);
        if (remainingPlayers.isEmpty()) {
            populateRemainingPlayers(nextPlayers);
        }
    }
    return new Round(game, nextPlayers);
}


There are some things that are not intuitive on your while body.


addPlayers update playersRemaining, letting us being uncertain on when the while exits
seems that populateRemainingPlayers can run more than once
You are adding players from remainingPlayers in populateRemainingPlayers and removing on addPlayers 
and in the end it's hard to tell what you are really trying to achieve on your algorithm





public void addPlayers(List&lt;Player&gt; players) {
    this.allPlayers.addAll(players);
    this.remainingPlayers.addAll(players);
    Collections.shuffle(this.remainingPlayers, new Random(1));    
}



If the game had a list of all players you probably could initialize the lists on the constructor instead of doing it on this method. 



I tend to hate method that receive a list as a parameter and then add elements to it (like addPlayers(List&lt;Player&gt; nextPlayers, int playersRemaining)), but I have some good reasons to do so:


The caller needs to instantiate the list
The method that receives the list may need to call other methods with that same list
Which means that elements can be added to the list in different methods
You should separate data handling from logic handling
You can test more easily a method that returns a list that is ready to use




Let's resume your need so we can achieve a better solution. 
It may be a little bit bad worded but I did the best I could.


If the game does not allowDuplicatePlayers then the player list should have enough players to play the game, however it may have more
If the list have more players than necessary, then we want to keep track of the last player that was able "to play", so all the players are able to play
After all the players have played it seems you want to have a different player order
If the game allowDuplicatePlayers you can take as many players needed from the player list sequentially, going back to start if necessary taking the same player more than once


Let's try to implement something along those lines:

private int idxLastPlayer;
public Round nextRound() {
    return new Round(game, getNextPlayers());
}

private List&lt;Player&gt; getNextPlayers(){
    int playersRemaining = getPlayerCount();
    List&lt;Player&gt; players = new ArrayList&lt;Player&gt;();
    for(int i = 0; i &lt; playersRemaining; ++i){
        //with idxLastPlayer we still round robin players if game.allowDuplicatePlayers()
        int idx = (i + idxLastPlayer)%remainingPlayers.size();
        players.add(remainingPlayers.get(idx));
        ++idxLastPlayer;
    }
    updateRemainingPlayers();
    return players;
}

private void updateRemainingPlayers(){
    int playersRemaining = getPlayerCount();
    if (!game.allowDuplicatePlayers()){
        int nPlayers = Math.max(playersRemaining, remainingPlayers.size() - 1);
        for(int i = 0; i &lt; nPlayers; ++i){
            remainingPlayers.remove(0); //removing from the start it's not the best but let's not worry about it
        }
    }else{
        //we can keep remainingPlayers always on the same state
        //Todo Should we shuffle players when we reach the end? But we could leap to an index &gt; 0...
        return;
    }
    //if we no longer have enough players we need to reset remainingPlayers with allPlayers 
    if(remainingPlayers.size() == 0 || 
        remainingPlayers.size() &lt; playersRemaining &amp;&amp; !game.allowDuplicatePlayers()) //this will discard the last allPlayers.size() % playersRemaining players
    {
        //Todo prioritize the players that were still on remainingPlayers?
        remainingPlayers = new ArrayList&lt;Player&gt;(allPlayers);
        //Start the next round on a different order
        Collections.shuffle(this.remainingPlayers, new Random(1));
    }
}