A Java 11+ client for the APIs used by Epic Games Launcher and the Fortnite client
- Authentication with Epic's APIs is managed internally
- Fetching and filtering (by platform, party type, and time window) of an account's Battle Royale statistics
- Fetching of all-time win leader boards using different combinations of platform and party type
- Fetching of a single account via its username or many accounts via their IDs
- Adding and removing friends, accepting and declining friend requests
- Fortnite EULA auto-accepting
- Two-factor authentication
<properties>
...
<!-- Use the latest version whenever possible. -->
<fortnite-2.version>2.0.0</fortnite-2.version>
...
</properties>
<dependencies>
...
<dependency>
<groupId>io.github.robertograham</groupId>
<artifactId>fortnite-2</artifactId>
<version>${fortnite-2.version}</version>
</dependency>
...
</dependencies>
This is the simplest way to instantiate a client:
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
final var fortnite = builder.build();
}
}
If Epic Games ever deprecate this library's default launcher and client tokens, you may provide your own like this:
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword")
.setEpicGamesLauncherToken("launcherToken")
.setFortniteClientToken("clientToken");
final var fortnite = builder.build();
}
}
Fortnite's EULA can be automatically accepted and access can be granted to Fortnite's APIs like so:
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
public final class Main {
public static void main(final String[] args) {
try (final var fortnite = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword")
.setAutoAcceptEulaAndGrantAccess(true)
.build()) {
}
}
}
When you no longer need your client instance, remember to terminate your Epic Games authentication session and release
the client's underlying resources with a call to Fortnite.close()
. Usage examples further in this document will make
this call implicitly using try
-with-resources statements.
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
final var fortnite = builder.build();
fortnite.close();
}
}
import io.github.robertograham.fortnite2.domain.Account;
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
final var optionalAccount = fortnite.account()
.findOneByDisplayName("RobertoGraham");
// nothing printed if the response was empty
optionalAccount.map(Account::accountId)
.ifPresent(System.out::println);
optionalAccount.map(Account::displayName)
.ifPresent(System.out::println);
} catch (final IOException exception) {
// findOneByDisplayName unexpected response
}
}
}
import io.github.robertograham.fortnite2.domain.Account;
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
final var optionalAccount = fortnite.account()
.findOneBySessionAccountId();
// nothing printed if the response was empty
optionalAccount.map(Account::accountId)
.ifPresent(System.out::println);
optionalAccount.map(Account::displayName)
.ifPresent(System.out::println);
} catch (final IOException exception) {
// findOneBySessionAccountId unexpected response
}
}
}
import io.github.robertograham.fortnite2.domain.Account;
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
import java.util.Collections;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
final var accountId1String = fortnite.account()
.findOneByDisplayName("RobertoGraham")
.map(Account::accountId)
.orElse("");
final var accountId2String = fortnite.account()
.findOneByDisplayName("Ninja")
.map(Account::accountId)
.orElse("");
// accountSet will be empty if the response was empty
// OR if every account ID was invalid
final var accountSet = fortnite.account()
.findAllByAccountIds(accountId1String, accountId2String)
.orElseGet(Collections::emptySet);
} catch (final IOException exception) {
// findOneByDisplayName unexpected response
// OR findAllByAccountIds unexpected response
}
}
}
Most basic filtering - by time windows
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
final var account = fortnite.account()
.findOneByDisplayName("RobertoGraham")
.orElseThrow();
final var accountIdString = account.accountId();
// if any null then we received an empty response
final var fromAccountAllTimeFilterableStatistic = fortnite.statistic()
.findAllByAccountForAllTime(account)
.orElse(null);
final var fromAccountIdAllTimeFilterableStatistic = fortnite.statistic()
.findAllByAccountIdForAllTime(accountIdString)
.orElse(null);
final var fromAccountCurrentSeasonFilterableStatistic = fortnite.statistic()
.findAllByAccountForCurrentSeason(account)
.orElse(null);
final var fromAccountIdCurrentSeasonFilterableStatistic = fortnite.statistic()
.findAllByAccountIdForCurrentSeason(accountIdString)
.orElse(null);
final var fromSessionAccountIdAllTimeFilterableStatistic = fortnite.statistic()
.findAllBySessionAccountIdForAllTime()
.orElse(null);
final var fromSessionAccountIdCurrentSeasonFilterableStatistic = fortnite.statistic()
.findAllBySessionAccountIdForCurrentSeason()
.orElse(null);
} catch (final IOException exception) {
// findOneByDisplayName unexpected response
// OR findAllByAccountForAllTime unexpected response
// OR findAllByAccountIdForAllTime unexpected response
// OR findAllByAccountForCurrentSeason unexpected response
// OR findAllByAccountIdForCurrentSeason unexpected response
// OR findAllBySessionAccountIdForAllTime unexpected response
// OR findAllBySessionAccountIdForCurrentSeason unexpected response
}
}
}
Filtering by platform and then party type
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
import static io.github.robertograham.fortnite2.domain.enumeration.PartyType.SQUAD;
import static io.github.robertograham.fortnite2.domain.enumeration.Platform.PC;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
final var account = fortnite.account()
.findOneByDisplayName("RobertoGraham")
.orElseThrow();
final var partyTypeFilterableStatistic = fortnite.statistic()
.findAllByAccountForAllTime(account)
.map(filterableStatistic -> filterableStatistic.byPlatform(PC))
.orElseThrow();
final var statistic = partyTypeFilterableStatistic.byPartyType(SQUAD);
} catch (final IOException exception) {
// findOneByDisplayName unexpected response
// OR findAllByAccountForAllTime unexpected response
}
}
}
Filtering by party type and then platform
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
import static io.github.robertograham.fortnite2.domain.enumeration.PartyType.SOLO;
import static io.github.robertograham.fortnite2.domain.enumeration.Platform.PS4;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
final var account = fortnite.account()
.findOneByDisplayName("RobertoGraham")
.orElseThrow();
final var platformFilterableStatistic = fortnite.statistic()
.findAllByAccountForAllTime(account)
.map(filterableStatistic -> filterableStatistic.byPartyType(SOLO))
.orElseThrow();
final var statistic = platformFilterableStatistic.byPlatform(PS4);
} catch (final IOException exception) {
// findOneByDisplayName unexpected response
// OR findAllByAccountForAllTime unexpected response
}
}
}
Inline platform and party type chained filter
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
import static io.github.robertograham.fortnite2.domain.enumeration.PartyType.DUO;
import static io.github.robertograham.fortnite2.domain.enumeration.Platform.XB1;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
final var account = fortnite.account()
.findOneByDisplayName("RobertoGraham")
.orElseThrow();
final var statistic = fortnite.statistic()
.findAllByAccountForAllTime(account)
.map((final var filterableStatistic) ->
filterableStatistic
.byPlatform(XB1)
.byPartyType(DUO)
)
.orElse(null);
} catch (final IOException exception) {
// findOneByDisplayName unexpected response
// OR findAllByAccountForAllTime unexpected response
}
}
}
FilterableStatistic
, PartyTypeFilterableStatistic
, and PlatformFilterableStatistic
all extend Statistic
. This
means that you can make calls like Statistic.kills()
, Statistic.wins()
, etc. at each filtering stage to get narrower
and narrower scoped values
import io.github.robertograham.fortnite2.domain.FilterableStatistic;
import io.github.robertograham.fortnite2.domain.PartyTypeFilterableStatistic;
import io.github.robertograham.fortnite2.domain.PlatformFilterableStatistic;
import io.github.robertograham.fortnite2.domain.Statistic;
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
import static io.github.robertograham.fortnite2.domain.enumeration.PartyType.SOLO;
import static io.github.robertograham.fortnite2.domain.enumeration.Platform.PC;
import static io.github.robertograham.fortnite2.domain.enumeration.Platform.PS4;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
final var account = fortnite.account()
.findOneByDisplayName("RobertoGraham")
.orElseThrow();
final var filterableStatisticOptional = fortnite.statistic()
.findAllByAccountForAllTime(account);
// prints 761 at time of writing
filterableStatisticOptional.map(FilterableStatistic::kills)
.ifPresent(System.out::println);
// prints 5 at time of writing
filterableStatisticOptional.map((final var filterableStatistic) -> filterableStatistic.byPlatform(PC))
.map(PartyTypeFilterableStatistic::kills)
.ifPresent(System.out::println);
// prints 580 at time of writing
filterableStatisticOptional.map((final var filterableStatistic) -> filterableStatistic.byPartyType(SOLO))
.map(PlatformFilterableStatistic::kills)
.ifPresent(System.out::println);
// prints 575 at time of writing
filterableStatisticOptional.map((final var filterableStatistic) ->
filterableStatistic
.byPlatform(PS4)
.byPartyType(SOLO)
)
.map(Statistic::kills)
.ifPresent(System.out::println);
} catch (final IOException exception) {
// findOneByDisplayName unexpected response
// OR findAllByAccountForAllTime unexpected response
}
}
}
Fetch the current season's wins leader board and print the accounts' names and wins. The example prints the following at the time of writing:
Name: JohnPitterTV, Wins: 350
Name: TTV SwitchUMG, Wins: 251
Name: Twitch TheBigOCE, Wins: 201
Name: WE_桃子, Wins: 197
Name: BlossoM Tsunami, Wins: 182
import io.github.robertograham.fortnite2.domain.Account;
import io.github.robertograham.fortnite2.domain.LeaderBoardEntry;
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
import java.util.function.Function;
import java.util.stream.Collectors;
import static io.github.robertograham.fortnite2.domain.enumeration.PartyType.SOLO;
import static io.github.robertograham.fortnite2.domain.enumeration.Platform.PC;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
final var leaderBoardEntryList = fortnite.leaderBoard()
.findHighestWinnersByPlatformAndByPartyTypeForCurrentSeason(PC, SOLO, 5)
.orElseThrow();
final var accountIdStringToAccountMap = fortnite.account()
.findAllByAccountIds(leaderBoardEntryList.stream()
.map(LeaderBoardEntry::accountId)
.map((final var accountIdString) -> accountIdString.replaceAll("-", ""))
.toArray(String[]::new))
.map((final var accountSet) ->
accountSet.stream()
.collect(Collectors.toMap(Account::accountId, Function.identity()))
)
.orElseThrow();
leaderBoardEntryList.stream()
.map((final var leaderBoardEntry) ->
String.format(
"Name: %s, Wins: %d",
accountIdStringToAccountMap.get(leaderBoardEntry.accountId().replaceAll("-", "")).displayName(),
leaderBoardEntry.value()
)
)
.forEach(System.out::println);
} catch (final IOException exception) {
// findHighestWinnersByPlatformAndByPartyTypeForCurrentSeason unexpected response
// OR findAllByAccountIds unexpected response
}
}
}
Fetch both accepted and pending friend requests for the authenticated user
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
fortnite.friend()
.findAllRequestsBySessionAccountId()
.ifPresent(System.out::println);
} catch (final IOException exception) {
// findAllRequestsBySessionAccountId unexpected response
}
}
}
Fetch only accepted friend requests for the authenticated user
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
fortnite.friend()
.findAllNonPendingRequestsBySessionAccountId()
.ifPresent(System.out::println);
} catch (final IOException exception) {
// findAllNonPendingRequestsBySessionAccountId unexpected response
}
}
}
Delete a friend or a friend request using an account or an account ID
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
final var account = fortnite.account()
.findOneByDisplayName("RobertoGraham")
.orElseThrow();
fortnite.friend()
.deleteOneByAccount(account);
fortnite.friend()
.deleteOneByAccountId(account.accountId());
} catch (final IOException exception) {
// findOneByDisplayName unexpected response
// OR deleteOneByAccount unexpected response
// OR deleteOneByAccountId unexpected response
}
}
}
Add a friend or accept a friend request using an account or an account ID
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;
import java.io.IOException;
public class Main {
public static void main(final String[] args) {
final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
try (final var fortnite = builder.build()) {
final var account = fortnite.account()
.findOneByDisplayName("RobertoGraham")
.orElseThrow();
fortnite.friend()
.addOneByAccount(account);
fortnite.friend()
.addOneByAccountId(account.accountId());
} catch (final IOException exception) {
// findOneByDisplayName unexpected response
// OR addOneByAccount unexpected response
// OR addOneByAccountId unexpected response
}
}
}