DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Develop a Spring Boot REST API in AWS: PART 4 (CodePipeline / CI/CD)
  • Aggregating REST APIs Calls Using Apache Camel
  • Leveraging Salesforce Using Spring Boot
  • Spring Boot REST API Request Body Validation Example Using a Custom Validator

Trending

  • Data Quality: A Novel Perspective for 2025
  • Advancing Your Software Engineering Career in 2025
  • Navigating and Modernizing Legacy Codebases: A Developer's Guide to AI-Assisted Code Understanding
  • Dropwizard vs. Micronaut: Unpacking the Best Framework for Microservices
  1. DZone
  2. Coding
  3. Frameworks
  4. Create a JSON API REST Service With Spring Boot and Elide

Create a JSON API REST Service With Spring Boot and Elide

Learn how to make a HSON API REST service using Elide and Spring Boot in this awesome tutorial. The JSON API is a great specification for developers, and is getting much more popular.

By 
Matthew Casperson user avatar
Matthew Casperson
·
Feb. 11, 16 · Analysis
Likes (10)
Comment
Save
Tweet
Share
71.1K Views

Join the DZone community and get the full member experience.

Join For Free

JSON API is a specification that developers can reference to create rich JSON REST APIs. The specification is growing in popularity, and there are a number of open source libraries being developed to make implementing and consuming a JSON API compliant REST service quick and easy.

One of these libraries is Elide, which is being developed by Yahoo engineers to expose Java JPA entities through a JSON API compatible interface. The nice thing about Elide is if you already have JPA entities, then with just a few lines of code you can expose them through a JSON API REST interface.

To demonstrate this, I’m going to show you how to build a read-only JSON API RESTful service using JPA, Elide and Spring Boot.

Our application will be a stock standard Spring Boot REST application. I’m not going to go into too much detail for the boilerplate classes that make up a Spring Boot application because the Spring documentation already does a great job for that. I’ll run through these classes quickly, highlighting any customizations that were made to facilitate the integration with Elide.

Application.java

package com.matthewcasperson.elidetest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * The entry point to our spring boot application
 */
@SpringBootApplication
@EnableTransactionManagement    // Elide requires transaction support, which we enable here
public class Application {
    public static void main(final String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

This is the entry point to the Spring boot application. The only change required to this class is the addition of the @EnableTransactionManagement annotation, which configures our application with transaction support. Transactions are required by Elide to function correctly.

ElideTest.java

package com.matthewcasperson.elidetest;

import com.yahoo.elide.Elide;
import com.yahoo.elide.ElideResponse;
import com.yahoo.elide.audit.Logger;
import com.yahoo.elide.audit.Slf4jLogger;
import com.yahoo.elide.core.DataStore;
import com.yahoo.elide.datastores.hibernate5.HibernateStore;
import org.hibernate.SessionFactory;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.HandlerMapping;

import javax.persistence.EntityManagerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import java.util.Map;

/**
 * The rest interface
 */
@RestController
public class ElideTest {

    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ElideTest.class);

    @Autowired
    private EntityManagerFactory emf;

    /**
     * Converts a plain map to a multivalued map
     * @param input The original map
     * @return A MultivaluedMap constructed from the input
     */
    private MultivaluedMap<String, String> fromMap(final Map<String, String> input) {
        return new MultivaluedHashMap<String, String>(input);
    }

    @CrossOrigin(origins = "*")
    @RequestMapping(
            method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE,
            value={"/{entity}", "/{entity}/{id}/relationships/{entity2}", "/{entity}/{id}/{child}", "/{entity}/{id}"})
    @Transactional
    public String jsonApiGet(@RequestParam final Map<String, String> allRequestParams, final HttpServletRequest request) {
        /*
            This gives us the full path that was used to call this endpoint.
         */
        final String restOfTheUrl = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);

        /*
            Elide works with the Hibernate SessionFactory, not the JPA EntityManagerFactory.
            Fortunately we san unwrap the JPA EntityManagerFactory to get access to the
            Hibernate SessionFactory.
         */
        final SessionFactory sessionFactory = emf.unwrap(SessionFactory.class);

        /*
            Elide takes a hibernate session factory
        */
        final DataStore dataStore = new HibernateStore(sessionFactory);

        /*
            Define a logger
         */
        final Logger logger = new Slf4jLogger();

        /*
            Create the Elide object
         */
        final Elide elide = new Elide(logger, dataStore);

        /*
            Convert the map to a MultivaluedMap, which we can then pass to Elide
         */
        final MultivaluedMap<String, String> params = fromMap(allRequestParams);

        /*
            There is a bug in Elide on Windows that will convert a leading forward slash into a backslash,
            which then displays the error "token recognition error at: '\\'".
         */
        final String fixedPath = restOfTheUrl.replaceAll("^/", "");

        /*
            This is where the magic happens. We pass in the path, the params, and a place holder security
            object (which we won't be using here in any meaningful way, but can be used by Elide
            to authorize certain actions), and we get back a response with all the information
            we need.
         */
        final ElideResponse response = elide.get(fixedPath, params, new Object());

        /*
            Return the JSON response to the client
         */
        return response.getBody();
    }
}

This is our REST interface, and it is where we glue Spring and Elide together.

There are a few important things to note about this class.

The first is that we have one single method handling a number of JSON API endpoints. As you can see, we have one method responding to the following URL patterns:

  • /{entity}, which returns a collection of entities

  • /{entity}/{id}, which returns a single entity

  • /{entity}/{id}/{child}, which returns the child entities of a parent entity

  • /{entity}/{id}/relationships/{entity2}, which returns the details of the children of a parent

It’s important to note that we don’t have to do anything more than respond to these URLs. Elide takes the URL and returns the correct information. All we do is take the request made via Spring, pass it to Elide, and return the result.

The second thing to note is that we have added the @Transactional annotation to the method. This instructs Spring to create a transaction that Elide will then use.

The third important step we take here is converting the JPA EntityManagerFactory to a Hibernate SessionFactory. Elide specifically uses the Hibernate SessionFactory, which thankfully is trivial to convert from the EntityManagerFactory that Spring has injected for us.

Finally, you’ll note that I have altered the path to strip off the leading forward slash. This is because of a bug in Elide under Windows (specifically with the Elide.parse() method and its use of Paths.get(path)) that will convert the leading forward slash to a backslash, which then leads to an exception. Altering the path in this way was a simple workaround that allowed me to validate my code on a Windows machine before uploading it to a Linux server (Linux is not affected by this bug).

Child.java

package com.matthewcasperson.elidetest.jpa;

import com.fasterxml.jackson.annotation.JsonBackReference;

import java.io.Serializable;
import javax.persistence.*;


/**
 * The persistent class for the child database table.
 * 
 */
@Entity
@Table(name="child")
@NamedQuery(name="Child.findAll", query="SELECT c FROM Child c")
public class Child implements Serializable {
private static final long serialVersionUID = 1L;

private Integer id;

private String description;

private String name;

private Parent parent;

public Child() {
}

@Id
@Column(unique=true, nullable=false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return this.id;
}

public void setId(Integer id) {
this.id = id;
}

@Column(length=45)
public String getDescription() {
return this.description;
}

public void setDescription(String description) {
this.description = description;
}

@Column(length=45)
public String getName() {
return this.name;
}

public void setName(String name) {
this.name = name;
}

//bi-directional many-to-one association to Parent
@ManyToOne
@JoinColumn(name="parentId", nullable=false)
@JsonBackReference
public Parent getParent() {
return this.parent;
}

public void setParent(Parent parent) {
this.parent = parent;
}

}

This class is a stock standard JPA entity. The only customization here is the addition of the Jackson @JsonBackReference annotation to prevent a serialization cycle from throwing an exception.

Note that you do need to put the JPA annotations on the methods rather that the fields. If you annotate the fields (which is completely legal JPA syntax), Elide will not function correctly.

Parent.java

package com.matthewcasperson.elidetest.jpa;

import com.fasterxml.jackson.annotation.JsonManagedReference;

import java.io.Serializable;
import javax.persistence.*;
import java.util.List;


/**
 * The persistent class for the parent database table.
 * 
 */
@Entity
@Table(name="parent")
@NamedQuery(name="Parent.findAll", query="SELECT p FROM Parent p")
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;

private Integer id;

private String description;

private String name;

private List<Child> children;

public Parent() {
}

@Id
@Column(unique=true, nullable=false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return this.id;
}

public void setId(Integer id) {
this.id = id;
}

@Column(length=45)
public String getDescription() {
return this.description;
}

public void setDescription(String description) {
this.description = description;
}

@Column(length=45)
public String getName() {
return this.name;
}

public void setName(String name) {
this.name = name;
}

//bi-directional many-to-one association to Child
@OneToMany(mappedBy="parent")
@JsonManagedReference
public List<Child> getChildren() {
return this.children;
}

public void setChildren(List<Child> children) {
this.children = children;
}

public Child addChild(Child child) {
getChildren().add(child);
child.setParent(this);

return child;
}

public Child removeChild(Child child) {
getChildren().remove(child);
child.setParent(null);

return child;
}

}

Again, this is a pretty standard JPA entity class. We have added a Jackson @JsonManagedReference to match the annotation we defined in the Child class.

package-info.java

/**
 * The JPA entities that are exposed to Elide via the @Include annotation
 */
@Include(rootLevel=true)
package com.matthewcasperson.elidetest.jpa;

import com.yahoo.elide.annotation.Include;

In order for Elide to have visibility on the JPA entities, we add a @Include(rootLevel=true) annotation on the package documented in the package-info.java file.

application.properties

server.port = 8080
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/elidetest
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext

spring.datasource.max-active=5
spring.datasource.max-idle=1
spring.datasource.min-idle=1
spring.datasource.initial-size=1
spring.datasource.testOnBorrow=true
spring.datasource.validationQuery=SELECT 1

This is a file used by Spring boot to configure aspects of the web server. We have used it here to define a connection to the database.

The last few settings (from line 9 onward) are optional and were added to allow the application to work when deployed to Heroku. You don’t need these if you are running the app locally. You can find more information about these settings in the Spring documentation.

Procfile

web: java -Dserver.port=$PORT $JAVA_OPTS -jar build/libs/spring-boot-jpa-elide-0.0.1.jar

This is a file use by Heroku to launch a Spring Boot application. You can get more information on this file from the Heroku documentation.

Viewing the Results

Let’s take a look at the various JSON API endpoints that we have just exposed. I have taken the liberty of uploading a version of this application to Heroku, so you can see the results of these URLs live.

  • https://elide-test.herokuapp.com/parent will display a list of the parent entities

  • https://elide-test.herokuapp.com/parent/1 will display the details of a single parent entity

  • https://elide-test.herokuapp.com/parent/1/relationships/children will display the type and id of the children assigned to a single parent

  • https://elide-test.herokuapp.com/parent/1/children will display the complete details of the children assigned to a single parent

We also get a number of useful query parameters that can be used to customize the output:

  • https://elide-test.herokuapp.com/parent?fields[parent]=name will display only the name attribute of the parent entities (this is known as sparse fieldsets in the JSON API spec)

  • https://elide-test.herokuapp.com/parent?filter[parent.id]=2 will return the parent entity with the id of 2

  • https://elide-test.herokuapp.com/parent?filter[parent.name][prefix]=Jane will return the parent entities with the name attribute starting with “Jane”

  • https://elide-test.herokuapp.com/parent?sort=-name will return the parent entities sorted by the name attribute in descending order

You can find more information on the format of these query parameters in the Elide documentation.

The fact that you get all this functionality for free using Elide is incredibly cool, and will save you an immense amount of work. Believe me, before discovering Elide I went about implementing my own JSON API service, and it was not an easy task. So to get this functionality with just a few dozen lines of code is awesome.

You can download the source code for this application from GitHub.

Spring Framework JSON API REST Web Protocols Spring Boot

Opinions expressed by DZone contributors are their own.

Related

  • Develop a Spring Boot REST API in AWS: PART 4 (CodePipeline / CI/CD)
  • Aggregating REST APIs Calls Using Apache Camel
  • Leveraging Salesforce Using Spring Boot
  • Spring Boot REST API Request Body Validation Example Using a Custom Validator

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!