Spring HATEOAS Pagination Links

Learn to build automatic pagination links in Spring Hateaos application using PagedModel and PagedResourcesAssembler classes.

1. JPA PagingAndSortingRepository

First thing, we need to use PagingAndSortingRepository repository which provides methods to retrieve entities using the pagination and sorting abstraction.

It is important because we do not want to rewrite the JPA queries to read data in a paging fashion, as it is available just by implementing this simple interface.

import org.springframework.data.repository.PagingAndSortingRepository;
import com.howtodoinjava.rest.entity.AlbumEntity;

public interface AlbumRepository extends PagingAndSortingRepository<AlbumEntity, Long>{

}

2. Pagination with PagedModel using PagedResourcesAssembler

  • To enable automatic pagination links, we must use PagedModel provided by spring hateoas module which helps in creating representations of pageable collections.
  • PagedResourcesAssembler accepts the JPA entities list, and converts it to PagedModel.
  • Additionally, we can use RepresentationModelAssembler to convert JPA entities into CollectionModel having custom resource representation.
  • Finally, PagedModel is returned as the API response from the REST controller.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.PagedModel;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.howtodoinjava.rest.assembers.AlbumModelAssembler;
import com.howtodoinjava.rest.entity.AlbumEntity;
import com.howtodoinjava.rest.model.AlbumModel;
import com.howtodoinjava.rest.repository.AlbumRepository;

@RestController
public class WebController
{
	@Autowired
	private AlbumRepository albumRepository;

	@Autowired
	private AlbumModelAssembler albumModelAssembler;

	@Autowired
	private PagedResourcesAssembler<AlbumEntity> pagedResourcesAssembler;

	@GetMapping("/api/albums-list")
	public ResponseEntity<PagedModel<AlbumModel>> getAllAlbums(Pageable pageable) {

		Page<AlbumEntity> albumEntities = albumRepository.findAll(pageable);

		PagedModel<AlbumModel> pagedModel = pagedResourcesAssembler
							.toModel(albumEntities, albumModelAssembler);

		return new ResponseEntity<>(pagedModel, HttpStatus.OK);
	}
}

The AlbumModelAssembler converts a JPA entity to a DTO object (entity and collection representations). i.e. it converts AlbumEntity to AlbumModel.

@Component
public class AlbumModelAssembler 
  extends RepresentationModelAssemblerSupport<AlbumEntity, AlbumModel> {
 
  public AlbumModelAssembler() {
    super(WebController.class, AlbumModel.class);
  }
 
  @Override
  public AlbumModel toModel(AlbumEntity entity) 
  {
    AlbumModel albumModel = instantiateModel(entity);
     
    albumModel.add(linkTo(
        methodOn(WebController.class)
        .getActorById(entity.getId()))
        .withSelfRel());
     
    albumModel.setId(entity.getId());
    albumModel.setTitle(entity.getTitle());
    albumModel.setDescription(entity.getDescription());
    albumModel.setReleaseDate(entity.getReleaseDate());
    albumModel.setActors(toActorModel(entity.getActors()));
    return albumModel;
  }
   
  @Override
  public CollectionModel<AlbumModel> toCollectionModel(Iterable<? extends AlbumEntity> entities) 
  {
    CollectionModel<AlbumModel> actorModels = super.toCollectionModel(entities);
     
    actorModels.add(linkTo(methodOn(WebController.class).getAllAlbums()).withSelfRel());
     
    return actorModels;
  }
 
  private List<ActorModel> toActorModel(List<ActorEntity> actors) {
    if (actors.isEmpty())
      return Collections.emptyList();
 
    return actors.stream()
        .map(actor -> ActorModel.builder()
            .id(actor.getId())
            .firstName(actor.getFirstName())
            .lastName(actor.getLastName())
            .build()
            .add(linkTo(
                methodOn(WebController.class)
                .getActorById(actor.getId()))
                .withSelfRel()))
        .collect(Collectors.toList());
  }
}

3. Using EntityModel if Model and Entity is the Same Class

Note that if we are using the same class as Model and Entity, then we can skip copying the fields and the ModelAssembler becomes very simple. It is useful if we do not have similar fields in the entity and model and there is no problem in returning the entity representations from the controller.

It helps to avoid copying each field from entity to model class, unnecessarily.

@Component
public class AlbumModelAssembler implements RepresentationModelAssembler<Album, EntityModel<Album>> {

  @Override
  public EntityModel<Album> toModel(Album album) {

    return EntityModel.of(album,
      linkTo(methodOn(AlbumController.class).get(album.getId()))
        .withSelfRel());
  }
}

Rest everything remains same.

4. Verify Pagination Links

Run the application and invoke REST API with paging request parameters.

Page numbers start with zero (0).

  • http://localhost:8080/api/albums-list?page=1&size=2&sort=title,desc
{
    "links": [
        {
            "rel": "first",
            "href": "http://localhost:8080/api/albums-list?page=0&size=2&sort=title,desc"
        },
        {
            "rel": "prev",
            "href": "http://localhost:8080/api/albums-list?page=0&size=2&sort=title,desc"
        },
        {
            "rel": "self",
            "href": "http://localhost:8080/api/albums-list?page=1&size=2&sort=title,desc"
        },
        {
            "rel": "next",
            "href": "http://localhost:8080/api/albums-list?page=2&size=2&sort=title,desc"
        },
        {
            "rel": "last",
            "href": "http://localhost:8080/api/albums-list?page=4&size=2&sort=title,desc"
        }
    ],
    "content": [
        {
            "id": 7,
            "title": "Top Hits Vol 7",
            "description": "Top hits vol 7. description",
            "releaseDate": "10-03-1987",
            "actors": [
                {
                    "id": 4,
                    "firstName": "Janice",
                    "lastName": "Preston",
                    "links": [
                        {
                            "rel": "self",
                            "href": "http://localhost:8080/api/actors/4"
                        }
                    ]
                }
            ],
            "links": [
                {
                    "rel": "self",
                    "href": "http://localhost:8080/api/actors/7"
                }
            ]
        },
        {
            "id": 6,
            "title": "Top Hits Vol 6",
            "description": "Top hits vol 6. description",
            "releaseDate": "10-03-1986",
            "actors": [
                {
                    "id": 3,
                    "firstName": "Laverne",
                    "lastName": "Mann",
                    "links": [
                        {
                            "rel": "self",
                            "href": "http://localhost:8080/api/actors/3"
                        }
                    ]
                }
            ],
            "links": [
                {
                    "rel": "self",
                    "href": "http://localhost:8080/api/actors/6"
                }
            ]
        }
    ],
    "page": {
        "size": 2,
        "totalElements": 10,
        "totalPages": 5,
        "number": 1
    }
}

We can verify different pagination and sorting options in URL using query parameters and they will work.

Happy Learning !!

Sourcecode on Github

Comments

Subscribe
Notify of
guest
2 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode