Create a client and start making requests:
import io.github.rubeneekhof.lastfm.api.LastFmClient;
var apiKey = "your-api-key-here";
LastFmClient client = LastFmClient.create(apiKey);Most endpoints work the same way - you have three options depending on how many parameters you need:
Just the basics:
var artist = client.artists().getInfo("Cher");
var similar = client.artists().getSimilar("Cher");One or two extra options:
var artist = client.artists().getInfo("Cher", "en");
var artist = client.artists().getInfo("Cher", true);
var similar = client.artists().getSimilar("Cher", 10);
var similar = client.artists().getSimilar("Cher", false, 20);Everything else (including MBID):
import io.github.rubeneekhof.lastfm.application.ArtistGetInfoRequest;
var artist = client.artists().getInfo(
ArtistGetInfoRequest.artist("Cher")
.lang("en")
.autocorrect(true)
.username("myusername")
.build()
);
// or using MBID
var artist = client.artists().getInfo(
ArtistGetInfoRequest.mbid("bfcc6d75-a6a5-4bc6-8282-47aec8531818")
.build()
);Each endpoint has its own request builder class. You start by calling either artist("name") or mbid("id") depending on what you want to search by. This is required - you must provide either an artist name or MBID.
Then you chain optional parameters before calling build():
ArtistGetInfoRequest.artist("Cher") // required: start here
.lang("en") // optional: biography language (ISO 639 code)
.autocorrect(true) // optional: auto-fix typos (defaults vary by endpoint)
.username("myusername") // optional: include user-specific data
.build() // required: creates the request objectThe builder validates that you've provided either an artist name or MBID when you call build(). If you forget both, it'll throw an exception. All the chained methods return the builder itself, so you can call them in any order and skip any optional parameters you don't need.
For example, if you only need the language and nothing else:
var artist = client.artists().getInfo(
ArtistGetInfoRequest.artist("Cher")
.lang("en")
.build()
);Same pattern applies to all endpoints. When we add albums or tracks, they'll have their own builders like AlbumGetInfoRequest, TrackGetInfoRequest, etc.
Some Last.fm API endpoints require authentication (like track.scrobble). The authentication process involves three steps:
First, create a client with your API key and secret, then request a token:
String apiKey = "your-api-key";
String apiSecret = "your-api-secret";
LastFmClient client = LastFmClient.create(apiKey, apiSecret);
String token = client.auth().getToken();Generate the authorization URL and visit it in your browser to grant permission:
String authUrl = client.auth().getAuthorizationUrl(apiKey, token);
System.out.println("Visit this URL to authorize: " + authUrl);After granting permission, you'll see a success page. Important: Tokens are valid for 60 minutes and can only be used once.
Once the token is authorized, exchange it for a session key:
Session session = client.auth().getSession(token);
String sessionKey = session.key();Session keys don't expire! Save your session key and reuse it for all future authenticated requests. You only need to go through the token authorization process once.
Once you have a session key, create an authenticated client:
LastFmClient authenticatedClient = LastFmClient.createAuthenticated(
apiKey,
apiSecret,
sessionKey
);This client can now use authenticated endpoints like track.scrobble.
Scrobbling is the process of recording that you've listened to a track. The track.scrobble endpoint allows you to submit scrobbles to Last.fm.
The minimum required information is artist, track, and timestamp:
import io.github.rubeneekhof.lastfm.domain.model.scrobble.Scrobble;
// Create an authenticated client first
LastFmClient client = LastFmClient.createAuthenticated(apiKey, apiSecret, sessionKey);
// Scrobble a single track
long timestamp = System.currentTimeMillis() / 1000 - 60; // 1 minute ago
Scrobble scrobble = Scrobble.builder()
.artist("Radiohead")
.track("Creep")
.timestamp(timestamp)
.build();
ScrobbleResponse response = client.tracks().scrobble(scrobble);
System.out.println("Accepted: " + response.accepted() +", Ignored: "+response.ignored());You can include additional information like album, track number, duration, etc.:
Scrobble scrobble = Scrobble.builder()
.artist("The Beatles")
.track("Hey Jude")
.timestamp(System.currentTimeMillis() / 1000 - 120) // 2 minutes ago
.album("The Beatles 1967-1970")
.albumArtist("The Beatles")
.mbid("b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d")
.trackNumber(1)
.duration(431) // seconds
.chosenByUser(true)
.build();
ScrobbleResponse response = client.tracks().scrobble(scrobble);You can scrobble up to 50 tracks in a single request:
import io.github.rubeneekhof.lastfm.application.track.TrackScrobbleRequest;
Scrobble scrobble1 = Scrobble.builder()
.artist("Daft Punk")
.track("One More Time")
.timestamp(baseTimestamp)
.album("Discovery")
.build();
Scrobble scrobble2 = Scrobble.builder()
.artist("Daft Punk")
.track("Harder, Better, Faster, Stronger")
.timestamp(baseTimestamp - 60)
.album("Discovery")
.build();
TrackScrobbleRequest batch = TrackScrobbleRequest.builder()
.addScrobble(scrobble1)
.addScrobble(scrobble2)
.build();
ScrobbleResponse response = client.tracks().scrobble(batch);- Timestamps: Must be in UNIX timestamp format (seconds since epoch). Timestamps that are too old (more than ~14 days) or too new (in the future) will be ignored.
- Accepted vs Ignored: The response includes counts of accepted and ignored scrobbles. Check
response.results()to see details about each scrobble, including ignored message codes. - Rate Limits: Be mindful of Last.fm's rate limits when scrobbling multiple tracks.
- Session Key: You must use an authenticated client with a valid session key. Session keys don't expire, so save yours after the first authentication.
| Category | Method | Status | Test | Notes |
|---|---|---|---|---|
| Album | album.getInfo |
✅ | ✅ | |
album.addTags |
⏳ | ⏳ | ||
album.getTags |
⏳ | ⏳ | ||
album.getTopTags |
⏳ | ⏳ | ||
album.removeTag |
⏳ | ⏳ | ||
album.search |
⏳ | ⏳ | ||
| Artist | artist.getInfo |
✅ | ✅ | |
artist.getSimilar |
✅ | ✅ | ||
artist.getCorrection |
✅ | ✅ | ||
artist.addTags |
⏳ | ⏳ | ||
artist.getTags |
⏳ | ⏳ | ||
artist.getTopAlbums |
⏳ | ⏳ | ||
artist.getTopTags |
⏳ | ⏳ | ||
artist.getTopTracks |
⏳ | ⏳ | ||
artist.removeTag |
⏳ | ⏳ | ||
artist.search |
✅ | ✅ | ||
| Auth | auth.getMobileSession |
⏳ | ⏳ | |
auth.getSession |
✅ | ⏳ | ||
auth.getToken |
✅ | ⏳ | ||
| Chart | chart.getTopArtists |
✅ | ⏳ | |
chart.getTopTags |
⏳ | ⏳ | ||
chart.getTopTracks |
⏳ | ⏳ | ||
| Geo | geo.getTopArtists |
✅ | ⏳ | |
geo.getTopTracks |
⏳ | ⏳ | ||
| Library | library.getArtists |
✅ | ⏳ | |
| Tag | tag.getInfo |
✅ | ⏳ | |
tag.getSimilar |
❌ | ❌ | This API call is currently broken and returns an empty array as a response. | |
tag.getTopAlbums |
✅ | ⏳ | ||
tag.getTopArtists |
✅ | ⏳ | ||
tag.getTopTags |
⏳ | ⏳ | ||
tag.getTopTracks |
⏳ | ⏳ | ||
tag.getWeeklyChartList |
⏳ | ⏳ | ||
| Track | track.addTags |
⏳ | ⏳ | |
track.getCorrection |
⏳ | ⏳ | ||
track.getInfo |
✅ | ⏳ | ||
track.getSimilar |
⏳ | ⏳ | ||
track.getTags |
⏳ | ⏳ | ||
track.getTopTags |
⏳ | ⏳ | ||
track.love |
✅ | ❌ | This API call doesn't return anything so no test has been made (yet) | |
track.removeTag |
⏳ | ⏳ | ||
track.scrobble |
✅ | ⏳ | ||
track.search |
⏳ | ⏳ | ||
track.unlove |
✅ | ❌ | This API call doesn't return anything so no test has been made (yet) | |
track.updateNowPlaying |
⏳ | ⏳ | ||
| User | user.getFriends |
⏳ | ⏳ | |
user.getInfo |
✅ | ⏳ | ||
user.getLovedTracks |
⏳ | ⏳ | ||
user.getPersonalTags |
⏳ | ⏳ | ||
user.getRecentTracks |
⏳ | ⏳ | ||
user.getTopAlbums |
⏳ | ⏳ | ||
user.getTopArtists |
⏳ | ⏳ | ||
user.getTopTags |
⏳ | ⏳ | ||
user.getTopTracks |
⏳ | ⏳ | ||
user.getWeeklyAlbumChart |
⏳ | ⏳ | ||
user.getWeeklyArtistChart |
⏳ | ⏳ | ||
user.getWeeklyChartList |
⏳ | ⏳ | ||
user.getWeeklyTrackChart |
⏳ | ⏳ |
Legend: ✅ Implemented | ⏳ Not yet implemented | ❌ Can't/Won't be implemented
The codebase is organized into layers that depend inward. The domain layer is at the center and doesn't depend on anything external.
api/ - This is what users of the library interact with. LastFmClient is the entry point that wires everything together.
application/ - Contains services that handle business logic and validation. For example, ArtistService validates inputs and calls the gateway to fetch data.
domain/ - The core of the application. model/ contains your domain entities (like Artist), and port/ defines interfaces that the application layer uses. These interfaces are implemented in the infrastructure layer.
infrastructure/ - This is where external concerns live. gateway/ contains implementations of the domain interfaces. Each domain (artist, album, etc.) has its own folder with:
- A gateway implementation (e.g.,
ArtistGatewayImpl) - A mapper that converts Last.fm DTOs to domain models (e.g.,
ArtistMapper) - A
response/folder with the Last.fm API response DTOs
DTOs (Data Transfer Objects) are simple classes that match the structure of the Last.fm API responses. They're used to deserialize JSON from the API, and then the mapper converts them into domain models. This keeps the domain layer clean and independent of the API's structure.
The http/ package provides the HTTP client wrapper used by all gateways.