User Profiles and Avatars

Integrating Gravatar REST API with Spring Boot

In today's web applications, avatars are essential for creating personalized, visually rich user interfaces. However, asking users to repeatedly upload photos and fill out the same profile details across different platforms can be tedious and hard to maintain. Services like Gravatar simplify this by automatically populating profiles with publicly available data linked to a user's Gravatar account.

This article demonstrates how to integrate Gravatar into a Java-based Spring Boot application to enrich user profiles with minimal user input. It begins with an overview of what Gravatar is and how its REST API works. From there, it walks through setting up a Spring Boot project, defining a user profile model, implementing a REST API client to fetch Gravatar data, and shows how to build a simple user profile view using Thymeleaf. Finally, it discusses strategies to enhance your profile system beyond Gravatar, enabling richer functionality and improved adaptability.

Whether you're building a new user management system or enhancing an existing one, this integration can improve the user experience while reducing development time.

What Is Gravatar

Gravatar, short for Globally Recognized Avatar, is a service that allows users to associate a profile image and other personal information with their email address or Gravatar login. These avatars and profile details can then be used across numerous websites and applications, providing a consistent and recognizable identity for users without requiring them to upload images or fill out profiles repeatedly.

Once a user sets up their profile on the Gravatar platform, any participating site or app can retrieve the user's avatar and public profile data via Gravatar using the user's email hash or Gravatar login. This not only enhances user experience by reducing repetitive profile setup but also helps maintain consistency in how users appear across different platforms.

In addition to the avatar image, a Gravatar profile can include public details such as:

  • Display name
  • Description
  • Job title
  • Location
  • Social media links
  • Personal websites
  • and more...

This information can be a valuable asset for enriching user profiles within your own application. If you're curious to see how a Gravatar profile looks, here's a sample: https://gravatar.com/profilefortests.

Gravatar offers a REST API that lets developers fetch user profile data programmatically. Here's an example of a basic API request using curl:

curl -X GET "https://api.gravatar.com/v3/profiles/profilefortests"

You can also retrieve just the avatar image using a direct link to avatar.

In this article, we'll focus on how to fetch Gravatar profile data using the Gravatar REST API and integrate it into a Java Spring Boot application. You'll learn how to make API calls, handle responses, and seamlessly incorporate the fetched profile information into your application.

Below is a sample Thymeleaf profile view demonstrating the kind of result this integration can produce:

Sample Thymeleaf user profile view populated with data fetched from the Gravatar REST API

Sample Thymeleaf user profile view populated with data fetched from the Gravatar REST API

Setting up the Spring Boot project

The base structure for a Spring Boot application can be generated using Spring Initializr.

1. Go to https://start.spring.io.

2. Choose Maven Project, Java, and the desired Spring Boot version.

3. Fill in the Group and Artifact to match your needs.

4. Click Generate to download the project zip file.

5. Unzip the downloaded file to your working directory.

6. Open a terminal in the project folder and run:

mvnw spring-boot:run

Your Spring Boot application should start successfully, confirming that the setup is complete.

Next, we'll move on to Implementing the User Profile Model section.

Implementing the User Profile Model

To work effectively with Gravatar's REST API, we will first create Java class that mirrors the structure of the profile data returned by the API.

Let's start with a sample curl command from the Gravatar REST API documentation:

curl -X GET "https://api.gravatar.com/v3/profiles/{profileIdentifier}" -H "Authorization: Bearer $GRAVATAR_API_KEY"

For testing purposes, we will use the profile identifier profilefortests to get sample data. In this example, we will keep things simple by making an unauthenticated request. However, authentication will be discussed later in the Implementing the Gravatar REST API Client section.

If you want to try it out, you can use this command to fetch the sample profile data:

curl -X GET "https://api.gravatar.com/v3/profiles/profilefortests"

If you don't have curl installed, you can open this URL in your browser to view the response: https://api.gravatar.com/v3/profiles/profilefortests

The response should include user profile information in JSON format, similar to the example below:

{
    "hash": "ae3fd35ef91be9dbedef7073c34b81bca12b799d464632ae7d84c0030d2b6f17",
    "display_name": "Test Profile",
    "profile_url": "https://gravatar.com/profilefortests",
    "avatar_url": "https://1.gravatar.com/avatar/ae3fd35ef91be9dbedef7073c34b81bca12b799d464632ae7d84c0030d2b6f17",
    "avatar_alt_text": "Test Profile avatar",
    "location": "Warsaw, Poland",
    "description": "This is a test profile used for development purposes.",
    "job_title": "Software Developer",
    "company": "",
    "verified_accounts": [],
    "pronunciation": "",
    "pronouns": "",
    "section_visibility": {
      "hidden_contact_info": false,
      "hidden_feeds": false,
      "hidden_links": false,
      "hidden_interests": false,
      "hidden_wallet": false,
      "hidden_photos": false,
      "hidden_verified_accounts": false
    }
}

To deserialize this JSON response into usable objects within the application, we will create a GravatarProfile Java class annotated with Lombok's @Data. Let's start from adding Lombok Maven dependency:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>

With the dependency in place, we can now implement the GravatarProfile Java class:

@Data
public class GravatarProfile {

    @JsonProperty("hash")
    private String hash;

    @JsonProperty("display_name")
    private String displayName;

    @JsonProperty("profile_url")
    private String profileUrl;

    @JsonProperty("avatar_url")
    private String avatarUrl;

    @JsonProperty("avatar_alt_text")
    private String avatarAltText;

    @JsonProperty("location")
    private String location;

    @JsonProperty("description")
    private String description;

    @JsonProperty("job_title")
    private String jobTitle;

    @JsonProperty("company")
    private String company;

    @JsonProperty("verified_accounts")
    private List<VerifiedAccount> verifiedAccounts;

    @JsonProperty("pronunciation")
    private String pronunciation;

    @JsonProperty("pronouns")
    private String pronouns;

    @JsonProperty("background_color")
    private String backgroundColor;

    @JsonProperty("section_visibility")
    private SectionVisibility sectionVisibility;

    @Data
    public static class VerifiedAccount {

        @JsonProperty("service_type")
        private String serviceType;

        @JsonProperty("service_label")
        private String serviceLabel;

        @JsonProperty("service_icon")
        private String serviceIcon;

        @JsonProperty("url")
        private String url;

        @JsonProperty("is_hidden")
        private boolean isHidden;
    }

    @Data
    public static class SectionVisibility {

        @JsonProperty("hidden_contact_info")
        private boolean hiddenContactInfo;

        @JsonProperty("hidden_feeds")
        private boolean hiddenFeeds;

        @JsonProperty("hidden_links")
        private boolean hiddenLinks;

        @JsonProperty("hidden_interests")
        private boolean hiddenInterests;

        @JsonProperty("hidden_wallet")
        private boolean hiddenWallet;

        @JsonProperty("hidden_photos")
        private boolean hiddenPhotos;

        @JsonProperty("hidden_verified_accounts")
        private boolean hiddenVerifiedAccounts;
    }
}

Now that we have the user profile model, let's move on to the Implementing the Gravatar REST API Client section.

Implementing the Gravatar REST API Client

To communicate with Gravatar's REST API, we'll need to make HTTP requests from our Spring Boot application. At this point, it's a good idea to add the spring-boot-starter-web dependency to your pom.xml if it's not already included.:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

This starter provides everything we need to create REST clients and controllers, including the RestTemplate class we'll use to call Gravatar's API.

The Gravatar Profiles API allows us to retrieve user profile data by making a simple GET request to the endpoint. A sample request looks like this:

curl -X GET "https://api.gravatar.com/v3/profiles/profilefortests"

To consume this API in Java, we'll define a REST client that maps the JSON response into a GravatarProfile class we previously created. This class serves as the data model representing the response structure from the Gravatar API.

Here is a basic implementation of the GravatarClient:

@Component
public class GravatarClient {

    private final String gravatarProfileUrl;

    private final RestTemplate restTemplate;

    public GravatarClient(RestTemplate restTemplate, @Value("${demo.gravatar-api.url}") String gravatarApiUrl) {
        this.gravatarProfileUrl = String.format("%s/v3/profiles/{profileIdentifier}", gravatarApiUrl);
        this.restTemplate = restTemplate;
    }

    public GravatarProfile getProfile(String profileIdentifier) {
        return restTemplate.getForObject(gravatarProfileUrl, GravatarProfile.class, profileIdentifier);
    }

}

This implementation uses RestTemplate for the HTTP call. We can register it as a Spring Bean so it will passed to the GravatarClient:

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

This is a minimal implementation and does not use an API key. Fortunately, the Gravatar Profiles API is free and does not require authentication for basic usage. According to the Gravatar REST API documentation:

The Profiles API is free to use, and its rate limits are customizable based on your needs. While the default limits below are set to prevent misuse, allowing you to begin testing the API right away, you can apply for much higher limits to accommodate specific usage requirements for no additional costs.

Unauthenticated requests are currently limited to 100 per hour, which is typically sufficient for development and many small applications. Remember, you can cache responses to reduce the number of requests. If your application requires a higher rate limit or additional capabilities, refer to the Gravatar documentation for more details.

Once integrated, the application can retrieve user profile data from Gravatar with minimal setup. To configure this functionality, set the required demo.gravatar-api.url property in the application.yaml file as shown below:

demo:
    gravatar-api:
        url: https://api.gravatar.com

With the client implementation in place, the application is now ready to interact with the Gravatar Profiles API. Next, we'll cover two basic tests: a unit test and an integration test to verify that the client behaves correctly and handles API responses as expected.

Writing Gravatar Client Tests

This section demonstrates how to test the GravatarClient created earlier using JUnit. We'll cover both unit and integration tests to ensure the client behaves correctly in isolation and when interacting with external APIs.

For unit tests, we use Mockito to mock the RestTemplate dependency. This allows us to simulate HTTP responses without making actual network calls. The goal here is to test the logic inside the client itself, ensuring it correctly processes responses returned from the mocked RestTemplate.

Integration testing, on the other hand, goes one step further by validating how the GravatarClient interacts with an HTTP API. Instead of reaching out to the real Gravatar service, we use WireMock to stub the API. WireMock runs locally during tests and provides fully controllable responses to mimic the actual Gravatar API behavior. This allows us to test things like request/response formats and serialization without leaving the development environment.

The following Maven dependencies can be used to help with these tests:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.wiremock</groupId>
    <artifactId>wiremock-standalone</artifactId>
    <version>3.13.1</version>
    <scope>test</scope>
</dependency>

This basic unit test illustrates configuring the client and validating expected behavior with mocked data:

@ExtendWith(MockitoExtension.class)
class GravatarClientTest {

    @Mock
    private RestTemplate restTemplate;

    @InjectMocks
    private GravatarClient gravatarClient;

    @Test
    void shouldGetUserProfile() {
        // given profile identifier
        var profileIdentifier = "testProfileIdentifier";

        // and profile
        var profile = new GravatarProfile();
        profile.setDisplayName("Test user");

        //and such profile can be returned from Gravatar REST API
        when(restTemplate.getForObject(any(), eq(GravatarProfile.class), eq(profileIdentifier))).thenReturn(profile);

        // when information about profile is requested
        var result = gravatarClient.getProfile(profileIdentifier);

        // then expected data is returned
        assertNotNull(result);
        assertEquals(profile.getDisplayName(), result.getDisplayName());

        // and data has been retrieved via RestTemplate
        verify(restTemplate).getForObject(any(), eq(GravatarProfile.class), eq(profileIdentifier));
    }

}

For integration tests, the response stub is defined separately in a dedicated class to keep it clean and reusable:

public class GravatarStub {

    public static void stubTestGravatarProfileResponse() {
        var response = """
                {
                  "hash": "9dadf68d7cf63135232d6f0db1b343b8161987b3ac51e835e8e6a660c290dd84",
                  "display_name": "Test Profile",
                  "profile_url": "https://localhost/some/test/url/profilefortests",
                  "avatar_url": "https://localhost/some/test/avatar/testhashvalue",
                  "avatar_alt_text": "Test Profile avatar",
                  "location": "Test location",
                  "description": "This is a test profile used for development purposes.",
                  "job_title": "Test job title",
                  "company": "",
                  "verified_accounts": [
                    {
                      "service_type": "testservicetype",
                      "service_label": "testlabel",
                      "service_icon": "https://localhost/test/icon.svg",
                      "url": "https://localhost/test/url",
                      "is_hidden": false
                    }
                  ],
                  "pronunciation": "",
                  "pronouns": "he/him/his",
                  "section_visibility": {
                    "hidden_contact_info": false,
                    "hidden_feeds": false,
                    "hidden_links": false,
                    "hidden_interests": false,
                    "hidden_wallet": false,
                    "hidden_photos": false,
                    "hidden_verified_accounts": false
                  }
                }
                """;

        stubFor(get(urlEqualTo("/v3/profiles/profilefortests"))
                .willReturn(
                        aResponse()
                                .withHeader("Content-Type", "application/json")
                                .withBody(response)
                )
        );
    }

}

WireMock itself is configured, started, and stopped programmatically using JUnit's @BeforeAll, @BeforeEach, and @AfterAll annotations. This setup is also moved to a separate class to keep concerns separated and tests easy to read and maintain:

@SpringBootTest
@TestInstance(PER_CLASS)
public abstract class AbstractIntegrationTest {

    protected WireMockServer wireMockServer;

    @BeforeAll
    void startWireMock() {
        wireMockServer = new WireMockServer(8089);
        wireMockServer.start();
    }

    @BeforeEach
    void setup() {
        configureFor("localhost", wireMockServer.port());
    }

    @AfterAll
    void stopWireMock() {
        wireMockServer.stop();
    }

}

To complete the setup, an application.yaml file is placed under src/test/resources. It defines test-specific configuration, such as the base URL used by the GravatarClient during integration tests.

demo:
    gravatar-api:
        url: http://localhost:8089

With this foundation, writing the actual integration test becomes simple. A typical test calls the GravatarClient, receives the stubbed response, and verifies that the client parses it correctly:

public class GravatarClientIntegrationTest extends AbstractIntegrationTest {

    @Autowired
    private GravatarClient gravatarClient;

    @Test
    void shouldReturnGravatarProfile() {
        // given profile
        stubTestGravatarProfileResponse();

        //when profile is requested
        var profile = gravatarClient.getProfile("profilefortests");

        //then profile is returned
        assertNotNull(profile);
        assertEquals("Test Profile", profile.getDisplayName());
        assertEquals("9dadf68d7cf63135232d6f0db1b343b8161987b3ac51e835e8e6a660c290dd84", profile.getHash());
    }

}

All these tests can be executed as part of the standard Maven build lifecycle. Simply run:

mvnw clean install

This approach provides basic coverage for the client implementation, both in isolation and in a simulated real-world environment. It helps catch common issues early and increases reliability, provided the API remains unchanged .

Practical Scenarios for the Gravatar Client

The most common use case for Gravatar is retrieving a user's avatar image. While that alone can enhance user interfaces, the Gravatar Profiles API offers much more than just avatars.

A practical example is auto-filling user profile data during registration or onboarding. By querying Gravatar, you can fetch publicly available information such as name, bio, location, or associated social media accounts. This helps reduce friction by sparing users from re-entering data they have already provided elsewhere.

Another valuable use case is profile synchronization. If a user updates their Gravatar profile by changing their avatar, bio, or linked accounts, your application can periodically refresh this data to keep local profiles up to date. This helps maintain a consistent identity across platforms.

Gravatar can also support user account verification. Since profiles may include verified services like LinkedIn or GitHub, this data can be used to confirm a public presence or check ownership of those accounts programmatically. Additionally, Gravatar data can serve as a lightweight signal of authenticity. While not a substitute for full identity verification, a consistent and established online presence linked to an email address can offer a basic level of trust, especially in community-based platforms.

In this demo, we will keep things simple. The focus will be on fetching profile data using the GravatarClient and displaying it in the UI through a basic Spring MVC controller integrated with Thymeleaf.

Integrating Gravatar Client with Thymeleaf

Thymeleaf is a versatile Java templating engine commonly used for rendering dynamic HTML content on the server side in web applications. In the context of a Spring Boot application, it allows you to dynamically generate HTML views directly on the server, without needing to rely on a separate frontend framework like React or Angular.

This is especially useful when building lightweight or server-rendered applications where simplicity and integration are more important than heavy interactivity. With Thymeleaf, it's easy to bind Java model data to templates and render complete views directly from Spring controllers.

The following Maven dependency can be used to enable Thymeleaf in a Spring Boot project:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Once Thymeleaf is included, integrating it with the Gravatar Client is straightforward. A Spring controller can be used to handle HTTP requests, fetch user profile data from Gravatar, and pass it to the view layer using the model:

@Controller
@AllArgsConstructor
public class ProfileController {

    private final GravatarClient gravatarClient;

    @GetMapping("/profile/{profileIdentifier}")
    public String getProfile(@PathVariable String profileIdentifier, Model model) {
        var profile = gravatarClient.getProfile(profileIdentifier);
        model.addAttribute("profile", profile);
        return "profile";
    }

}

The corresponding HTML template will be responsible for rendering the data. By default, Thymeleaf templates should be placed in the src/main/resources/templates directory. This location is required by Spring Boot's auto-configuration, so make sure to place your .html files there to ensure they are correctly resolved. Here's what the template might look like:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Gravatar Profile</title>
    <link rel="stylesheet" th:href="@{/css/styles.css}"/>
</head>
<body>
<div class="container">
    <img th:src="${profile.avatarUrl}" th:alt="${profile.avatarAltText}"/>
    <h1 th:text="${profile.displayName}"/>
    <h2 th:text="${profile.jobTitle}"/>
    <p th:text="${profile.location}" th:class="location"/>
    <p th:text="${profile.description}" th:class="description"/>
</div>
</body>
</html>

To style the rendered HTML content, you can include a custom CSS file. In this example, it is located at src/main/resources/static/css/style.css. By default, static files are served from the static directory, so the stylesheet can be linked directly in the HTML template. Here's a basic example:

body {
    font-family: Arial, sans-serif;
    background-color: #26363f;
    color: #fff;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    margin: 0;
}

h1 {
    margin: 10px;
    padding-top: 5px;
}

h2 {
    margin: 5px;
}

.container {
    max-width: 500px;
    padding: 25px 50px;
    background: #0d191e;
    border-radius: 8px;
    text-align: center;
}

.container img {
    border-radius: 50%;
}

.location {
    color: #ccc;
    font-weight: 600;
}

.description {
    color: #ccc;
}

With the controller and template in place, you can now run the application:

mvnw spring-boot:run

Once the application is running, open a browser and navigate to:

http://localhost:8080/profile/{profileIdentifier}

For example, visiting http://localhost:8080/profile/profilefortests will display a test profile. You can also try using a Gravatar profile identifier that matches your own account to see your personal data rendered in the view.

When loaded, the profile page displays the user's avatar, name, bio, and more, as shown in the image below:

Sample Thymeleaf user profile view populated with data fetched from the Gravatar REST API

Sample Thymeleaf user profile view populated with data fetched from the Gravatar REST API

This simple integration showcases how powerful and seamless it can be to combine Spring Boot, Thymeleaf, and external services like Gravatar to create dynamic, personalized user experiences.

Beyond Gravatar

Gravatar offers a simple and effective way to manage avatars and basic profile information across platforms. In addition to profile pictures, it can provide bios, contact details, and links to verified accounts. However, in modern applications, user identity often requires more flexibility and customization. Users increasingly expect profile features that reflect not only who they are but also how they want to present themselves, with editable bios, custom avatars, extended social presence, and platform-specific context.

Platforms like about.me build on this idea by giving users more freedom to craft personal landing pages. These pages serve as digital business cards that showcase not just profile details but also personality, purpose, and a broader online presence. These services focus on customization and storytelling, allowing users to curate how they are represented across the web in a more expressive and controlled way.

Another great example is HackerRank, which allows users to create detailed profiles showcasing their coding skills, achievements, certifications, and badges earned through challenges and contests. This kind of profile goes beyond basic information and avatars by emphasizing accomplishments and expertise, making it valuable for both personal branding and professional opportunities.

For developers, this presents an opportunity to create more personalized and engaging user experiences. By supporting features such as editable bios, location fields, social media links, and the ability to upload custom avatars directly within your application, you can offer users greater flexibility and control. Gravatar does an excellent job of providing consistent avatar hosting and essential profile data, but when combined with richer application-specific fields or integrations with other identity services, it becomes part of a more complete and expressive profile system.

Using Gravatar is an excellent first step for adding avatar support with minimal effort. However, it is just one part of a larger picture. As your application grows and user expectations evolve, you may need to move toward a more comprehensive and flexible profile system that supports deeper personalization and offers a broader representation of user identity. This means thinking intentionally about digital identity and designing tools that allow users to express themselves meaningfully within your platform.

Conclusion

In this article, we explored how to integrate Gravatar into a Java and Spring Boot application, providing a straightforward yet powerful way to add avatar support to user profiles with minimal effort. Through practical examples, we demonstrated how to retrieve and display Gravatar data, test the integration, and use Thymeleaf for user-friendly rendering.

As highlighted earlier, Gravatar offers a solid foundation for managing avatars and essential profile information across platforms. However, modern users expect more flexibility and richer profiles that reflect both who they are and how they want to present themselves. Customizable bios, social media links, and deeper integrations with platforms like about.me and HackerRank can significantly enhance the user experience by allowing more expressive and personalized digital identities.

I use a few different platforms to share various aspects of my online presence. For example, my Gravatar profile helps me maintain a consistent avatar image across different sites. On my about.me page, I link to some of my online profiles and include a brief overview of my background. Finally, my HackerRank profile includes details like badges and certifications I've earned on the platform. Together, these profiles help me present a more rounded view of my interests and experience.

For developers, supporting such diverse identity sources within applications opens opportunities to create more engaging and customizable user experiences. Gravatar remains an excellent starting point, especially for avatar hosting and basic profile data, but integrating additional services or building richer, application-specific profile features will better meet evolving user expectations.

As your application grows, thinking strategically about digital identity will help you build platforms where users can truly express themselves in meaningful and personalized ways.

Additional Resources

Author


This article and the accompanying example code was written by Kamil Mazurek, a Software Engineer based in Warsaw, Poland. You can also find me on my LinkedIn profile.

My public repositories can be found on my GitHub and GitLab profiles:

Thanks for visiting


Disclaimer

THIS ARTICLE AND ANY SOFTWARE INCLUDED WITH THIS ARTICLE AND CREATED BY THE ARTICLE'S AUTHOR ARE PROVIDED FOR EDUCATIONAL PURPOSES ONLY.

THE ARTICLE AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE ARTICLE, THE SOFTWARE, OR THE USE OR OTHER DEALINGS IN THE ARTICLE OR SOFTWARE.

THIRD-PARTY LIBRARIES REFERENCED OR INCLUDED IN THIS SOFTWARE ARE SUBJECT TO THEIR OWN LICENSES. THIRD-PARTY DOCUMENTATION OR EXTERNAL RESOURCES REFERENCED IN THIS ARTICLE ARE SUBJECT TO THEIR OWN LICENSES AND TERMS.