In the 9 years of running Baeldung, we've never been through anything like this pandemic

And, if making my courses more affordable for a while is going to help you stay in business, land a new job, make rent or be able to provide for your family - then it's well worth doing.

Effective immediately, all Baeldung courses are 33% off their normal prices!

You'll find all three courses in the menu, above.

In the 9 years of running Baeldung, we've never been through anything like this pandemic

And, if making my courses more affordable for a while is going to help you stay in business, land a new job, make rent or be able to provide for your family - then it's well worth doing.

Effective immediately, all Baeldung courses are 33% off their normal prices!

You'll find all three courses in the menu, above.

1. Overview

In this article, we'll start exploring the JSON-API spec and how that can be integrated into a Spring backed REST API.

We'll use the Katharsis implementation of JSON-API in Java – and we'll set up a Katharsis powered Spring application – so all we need is a Spring application.

2. Maven

First, let's take a look at our maven configuration – we need to add the following dependency into our pom.xml:

<dependency> <groupId>io.katharsis</groupId> <artifactId>katharsis-spring</artifactId> <version>3.0.2</version> </dependency>

3. A User Resource

Next, let's take a look at our User resource:

@JsonApiResource(type = "users") public class User { @JsonApiId private Long id; private String name; private String email; }

Note that:

@JsonApiResource annotation is used to define our resource User

@JsonApiId annotation is used to define the resource identifier

And very briefly – the persistence for this example is going to be a Spring Data repository here:

public interface UserRepository extends JpaRepository<User, Long> {}

4. A Resource Repository

Next, let's discuss our resource repository – each resource should have a ResourceRepositoryV2 to publish the API operations available on it:

@Component public class UserResourceRepository implements ResourceRepositoryV2<User, Long> { @Autowired private UserRepository userRepository; @Override public User findOne(Long id, QuerySpec querySpec) { Optional<User> user = userRepository.findById(id); return user.isPresent()? user.get() : null; } @Override public ResourceList<User> findAll(QuerySpec querySpec) { return querySpec.apply(userRepository.findAll()); } @Override public ResourceList<User> findAll(Iterable<Long> ids, QuerySpec querySpec) { return querySpec.apply(userRepository.findAllById(ids)); } @Override public <S extends User> S save(S entity) { return userRepository.save(entity); } @Override public void delete(Long id) { userRepository.deleteById(id); } @Override public Class<User> getResourceClass() { return User.class; } @Override public <S extends User> S create(S entity) { return save(entity); } }

A quick note here – this is of course very similar to a Spring controller.

5. Katharsis Configuration

As we are using katharsis-spring, all we need to do is to import KatharsisConfigV3 in our Spring Boot Application:

@Import(KatharsisConfigV3.class)

And configure Katharsis parameters in our application.properties:

katharsis.domainName=http://localhost:8080 katharsis.pathPrefix=/

With that – we can now start consuming the API; for example:

GET “http://localhost:8080/users“: to get all users.

POST “http://localhost:8080/users“: to add new user, and more.

6. Relationships

Next, let's discuss how to handle entities relationships in our JSON API.

6.1. Role Resource

First, let's introduce a new resource – Role:

@JsonApiResource(type = "roles") public class Role { @JsonApiId private Long id; private String name; @JsonApiRelation private Set<User> users; }

And then set up a many-to-many relation between User and Role:

@JsonApiRelation(serialize=SerializeType.EAGER) private Set<Role> roles;

6.2. Role Resource Repository

Very quickly – here is our Role resource repository:

@Component public class RoleResourceRepository implements ResourceRepositoryV2<Role, Long> { @Autowired private RoleRepository roleRepository; @Override public Role findOne(Long id, QuerySpec querySpec) { Optional<Role> role = roleRepository.findById(id); return role.isPresent()? role.get() : null; } @Override public ResourceList<Role> findAll(QuerySpec querySpec) { return querySpec.apply(roleRepository.findAll()); } @Override public ResourceList<Role> findAll(Iterable<Long> ids, QuerySpec querySpec) { return querySpec.apply(roleRepository.findAllById(ids)); } @Override public <S extends Role> S save(S entity) { return roleRepository.save(entity); } @Override public void delete(Long id) { roleRepository.deleteById(id); } @Override public Class<Role> getResourceClass() { return Role.class; } @Override public <S extends Role> S create(S entity) { return save(entity); } }

It is important to understand here is that this single resource repo doesn't handle the relationship aspect – that takes a separate repository.

6.3. Relationship Repository

In order to handle the many-to-many relationship between User–Role we need to create a new style of repository:

@Component public class UserToRoleRelationshipRepository implements RelationshipRepositoryV2<User, Long, Role, Long> { @Autowired private UserRepository userRepository; @Autowired private RoleRepository roleRepository; @Override public void setRelation(User User, Long roleId, String fieldName) {} @Override public void setRelations(User user, Iterable<Long> roleIds, String fieldName) { Set<Role> roles = new HashSet<Role>(); roles.addAll(roleRepository.findAllById(roleIds)); user.setRoles(roles); userRepository.save(user); } @Override public void addRelations(User user, Iterable<Long> roleIds, String fieldName) { Set<Role> roles = user.getRoles(); roles.addAll(roleRepository.findAllById(roleIds)); user.setRoles(roles); userRepository.save(user); } @Override public void removeRelations(User user, Iterable<Long> roleIds, String fieldName) { Set<Role> roles = user.getRoles(); roles.removeAll(roleRepository.findAllById(roleIds)); user.setRoles(roles); userRepository.save(user); } @Override public Role findOneTarget(Long sourceId, String fieldName, QuerySpec querySpec) { return null; } @Override public ResourceList<Role> findManyTargets(Long sourceId, String fieldName, QuerySpec querySpec) { final Optional<User> userOptional = userRepository.findById(sourceId); User user = userOptional.isPresent() ? userOptional.get() : new User(); return querySpec.apply(user.getRoles()); } @Override public Class<User> getSourceResourceClass() { return User.class; } @Override public Class<Role> getTargetResourceClass() { return Role.class; } }

We're ignoring the singular methods here, in the relationship repository.

7. Test

Finally, let's analyze a few requests and really understand what the JSON-API output looks like.

We're going to start retrieving a single User resource (with id = 2):

GET http://localhost:8080/users/2

Takeaways:

The main attributes of the Resource are found in data.attributes

The main relationships of the Resource are found in data.relationships

As we used @JsonApiRelation(serialize=SerializeType.EAGER) for the roles relationship, it is included in the JSON and found in node included

Next – let's get the collection resource containing the Roles:

GET http://localhost:8080/roles

{ "data":[ { "type":"roles", "id":"1", "attributes":{ "name":"ROLE_USER" }, "relationships":{ "users":{ "links":{ "self":"http://localhost:8080/roles/1/relationships/users", "related":"http://localhost:8080/roles/1/users" } } }, "links":{ "self":"http://localhost:8080/roles/1" } }, { "type":"roles", "id":"2", "attributes":{ "name":"ROLE_ADMIN" }, "relationships":{ "users":{ "links":{ "self":"http://localhost:8080/roles/2/relationships/users", "related":"http://localhost:8080/roles/2/users" } } }, "links":{ "self":"http://localhost:8080/roles/2" } } ], "included":[ ] }

The quick take-away here is that we get all Roles in the system – as an array in the data node

8. Conclusion

JSON-API is a fantastic spec – finally adding some structure in the way we use JSON in our APIs and really powering a true Hypermedia API.

This piece explored one way to set it up in a Spring app. But regardless of that implementation, the spec itself is – in my view – very very promising work.

The complete source code for the example is available over on GitHub. It's a Maven project which can be imported and run as-is.