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
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

How does AI transform chaos engineering from an experiment into a critical capability? Learn how to effectively operationalize the chaos.

Data quality isn't just a technical issue: It impacts an organization's compliance, operational efficiency, and customer satisfaction.

Are you a front-end or full-stack developer frustrated by front-end distractions? Learn to move forward with tooling and clear boundaries.

Developer Experience: Demand to support engineering teams has risen, and there is a shift from traditional DevOps to workflow improvements.

Related

  • Spring Boot - How To Use Native SQL Queries | Restful Web Services
  • Develop a Spring Boot REST API in AWS: PART 4 (CodePipeline / CI/CD)
  • Building REST API Backend Easily With Ballerina Language
  • Composite Requests in Salesforce Are a Great Idea

Trending

  • AI Agent Architectures: Patterns, Applications, and Implementation Guide
  • How to Install and Set Up Jenkins With Docker Compose
  • Building Generative AI Services: An Introductory and Practical Guide
  • Memory Leak Due to Uncleared ThreadLocal Variables
  1. DZone
  2. Data Engineering
  3. Databases
  4. Building a REST API with JAXB, Spring Boot and Spring Data

Building a REST API with JAXB, Spring Boot and Spring Data

By 
Matt Raible user avatar
Matt Raible
·
Oct. 30, 14 · Tutorial
Likes (0)
Comment
Save
Tweet
Share
63.5K Views

Join the DZone community and get the full member experience.

Join For Free

if someone asked you to develop a rest api on the jvm, which frameworks would you use? i was recently tasked with such a project. my client asked me to implement a rest api to ingest requests from a 3rd party. the project entailed consuming xml requests, storing the data in a database, then exposing the data to internal application with a json endpoint. finally, it would allow taking in a json request and turning it into an xml request back to the 3rd party.

with the recent release of apache camel 2.14 and my success using it , i started by copying my apache camel / cxf / spring boot project and trimming it down to the bare essentials. i whipped together a simple hello world service using camel and spring mvc. i also integrated swagger into both. both implementations were pretty easy to create ( sample code ), but i decided to use spring mvc. my reasons were simple: its rest support was more mature, i knew it well, and spring mvc test makes it easy to test apis.

camel's swagger support without web.xml
as part of the aforementioned spike, i learned out how to configure camel's rest and swagger support using spring's javaconfig and no web.xml. i made this into a sample project and put it on github as camel-rest-swagger .

this article shows how i built a rest api with java 8, spring boot/mvc, jaxb and spring data (jpa and rest components). i stumbled a few times while developing this project, but figured out how to get over all the hurdles. i hope this helps the team that's now maintaining this project (my last day was friday) and those that are trying to do something similar.

xml to java with jaxb

the data we needed to ingest from a 3rd party was based on the ncpdp standards. as a member, we were able to download a number of xsd files, put them in our project and generate java classes to handle the incoming/outgoing requests. i used the maven-jaxb2-plugin to generate the java classes.

<plugin>
    <groupid>org.jvnet.jaxb2.maven2</groupid>
    <artifactid>maven-jaxb2-plugin</artifactid>
    <version>0.8.3</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <args>
                    <arg>-xtostring</arg>
                    <arg>-xequals</arg>
                    <arg>-xhashcode</arg>
                    <arg>-xcopyable</arg>
                </args>
                <plugins>
                    <plugin>
                        <groupid>org.jvnet.jaxb2_commons</groupid>
                        <artifactid>jaxb2-basics</artifactid>
                        <version>0.6.4</version>
                    </plugin>
                </plugins>
                <schemadirectory>src/main/resources/schemas/ncpdp</schemadirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

the first error i ran into was about a property already being defined.

[info] --- maven-jaxb2-plugin:0.8.3:generate (default) @ spring-app ---
[error] error while parsing schema(s).location [ file:/users/mraible/dev/spring-app/src/main/resources/schemas/ncpdp/structures.xsd{1811,48}].
com.sun.istack.saxparseexception2; systemid: file:/users/mraible/dev/spring-app/src/main/resources/schemas/ncpdp/structures.xsd;
    linenumber: 1811; columnnumber: 48; property "multipletimingmodifierandtimingandduration" is already defined.
    use <jaxb:property> to resolve this conflict.
at com.sun.tools.xjc.errorreceiver.error(errorreceiver.java:86)

i was able to workaround this by upgrading to maven-jaxb2-plugin version 0.9.1. i created a controller and stubbed out a response with hard-coded data. i confirmed the incoming xml-to-java marshalling worked by testing with a sample request provided by our 3rd party customer. i started with a curl command, because it was easy to use and could be run by anyone with the file and curl installed.

curl -x post -h 'accept: application/xml' -h 'content-type: application/xml' \
--data-binary @sample-request.xml http://localhost:8080/api/message -v

this is when i ran into another stumbling block: the response wasn't getting marshalled back to xml correctly. after some research, i found out this was caused by the lack of @xmlrootelement annotations on my generated classes. i posted a question to stack overflow titled returning jaxb-generated elements from spring boot controller . after banging my head against the wall for a couple days, i figured out the solution .

i created a bindings.xjb file in the same directory as my schemas. this causes jaxb to generate @xmlrootelement on classes.

<?xml version="1.0"?>
<jxb:bindings version="1.0"
              xmlns:xsd="http://www.w3.org/2001/xmlschema"
              xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
              xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
              xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
              xsi:schemalocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd">

    <jxb:bindings schemalocation="transport.xsd" node="/xsd:schema">
        <jxb:globalbindings>
            <xjc:simple/>
        </jxb:globalbindings>
    </jxb:bindings>
</jxb:bindings>

to add namespaces prefixes to the returned xml, i had to modify the maven-jaxb2-plugin to add a couple arguments.

<arg>-extension</arg>
<arg>-xnamespace-prefix</arg>

and add a dependency:

<dependencies>
    <dependency>
        <groupid>org.jvnet.jaxb2_commons</groupid>
        <artifactid>jaxb2-namespace-prefix</artifactid>
        <version>1.1</version>
    </dependency>
</dependencies>

then i modified bindings.xjb to include the package and prefix settings. i also moved <xjc:simple/> into a global setting. i eventually had to add prefixes for all schemas and their packages.

<?xml version="1.0"?>
<bindings version="2.0" xmlns:xsd="http://www.w3.org/2001/xmlschema" xmlns="http://java.sun.com/xml/ns/jaxb"
          xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
          xmlns:namespace="http://jaxb2-commons.dev.java.net/namespace-prefix"
          xsi:schemalocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd
              http://jaxb2-commons.dev.java.net/namespace-prefix http://java.net/projects/jaxb2-commons/sources/svn/content/namespace-prefix/trunk/src/main/resources/prefix-namespace-schema.xsd">

    <globalbindings>
        <xjc:simple/>
    </globalbindings>

    <bindings schemalocation="transport.xsd" node="/xsd:schema">
        <schemabindings>
            <package name="org.ncpdp.schema.transport"/>
        </schemabindings>
        <bindings>
            <namespace:prefix name="transport"/>
        </bindings>
    </bindings>
</bindings>

i learned how to add prefixes from the namespace-prefix plugins page .

finally, i customized the code-generation process to generate joda-time's datetime instead of the default xmlgregoriancalendar . this involved a couple custom xmladapters and a couple additional lines in bindings.xjb . you can see the adapters and bindings.xjb with all necessary prefixes in this gist . nicolas fränkel's customize your jaxb bindings was a great resource for making all this work.

i wrote a test to prove that the ingest api worked as desired.

@runwith(springjunit4classrunner.class)
@springapplicationconfiguration(classes = application.class)
@webappconfiguration
@dirtiescontext(classmode = dirtiescontext.classmode.after_class)
public class initiaterequestcontrollertest {

    @inject
    private initiaterequestcontroller controller;

    private mockmvc mockmvc;

    @before
    public void setup() {
        mockitoannotations.initmocks(this);
        this.mockmvc = mockmvcbuilders.standalonesetup(controller).build();
    }

    @test
    public void testgetnotallowedonmessagesapi() throws exception {
        mockmvc.perform(get("/api/initiate")
                .accept(mediatype.application_xml))
                .andexpect(status().ismethodnotallowed());
    }

    @test
    public void testpostpainitiationrequest() throws exception {
        string request = new scanner(new classpathresource("sample-request.xml").getfile()).usedelimiter("\\z").next();

        mockmvc.perform(post("/api/initiate")
                .accept(mediatype.application_xml)
                .contenttype(mediatype.application_xml)
                .content(request))
                .andexpect(status().isok())
                .andexpect(content().contenttype(mediatype.application_xml))
                .andexpect(xpath("/message/header/to").string("3rdparty"))
                .andexpect(xpath("/message/header/sendersoftware/sendersoftwaredeveloper").string("hid"))
                .andexpect(xpath("/message/body/status/code").string("010"));
    }
}

spring data for jpa and rest

with jaxb out of the way, i turned to creating an internal api that could be used by another application. spring data was fresh in my mind after reading about it last summer. i created classes for entities i wanted to persist, using lombok's @data to reduce boilerplate.

i read the accessing data with jpa guide, created a couple repositories and wrote some tests to prove they worked. i ran into an issue trying to persist joda's datetime and found jadira provided a solution.

i added its usertype.core as a dependency to my pom.xml:

<dependency>
    <groupid>org.jadira.usertype</groupid>
    <artifactid>usertype.core</artifactid>
    <version>3.2.0.ga</version>
</dependency>

... and annotated datetime variables accordingly.

@column(name = "last_modified", nullable = false)
@type(type="org.jadira.usertype.dateandtime.joda.persistentdatetime")
private datetime lastmodified;

with jpa working, i turned to exposing rest endpoints. i used accessing jpa data with rest as a guide and was looking at json in my browser in a matter of minutes. i was surprised to see a "profile" service listed next to mine, and posted a question to the spring boot team. oliver gierke provided an excellent answer .

swagger

spring mvc's integration for swagger has greatly improved since i last wrote about it . now you can enable it with a @enableswagger annotation. below is the swaggerconfig class i used to configure swagger and read properties from application.yml .

@configuration
@enableswagger
public class swaggerconfig implements environmentaware {
public static final string default_include_pattern = "/api/.*";

    private relaxedpropertyresolver propertyresolver;

    @override
    public void setenvironment(environment environment) {
        this.propertyresolver = new relaxedpropertyresolver(environment, "swagger.");
    }

    /**
     * swagger spring mvc configuration
     */
    @bean
    public swaggerspringmvcplugin swaggerspringmvcplugin(springswaggerconfig springswaggerconfig) {
        return new swaggerspringmvcplugin(springswaggerconfig)
                .apiinfo(apiinfo())
                .genericmodelsubstitutes(responseentity.class)
                .includepatterns(default_include_pattern);
    }

    /**
     * api info as it appears on the swagger-ui page
     */
    private apiinfo apiinfo() {
        return new apiinfo(
            propertyresolver.getproperty("title"),
            propertyresolver.getproperty("description"),
            propertyresolver.getproperty("termsofserviceurl"),
            propertyresolver.getproperty("contact"),
            propertyresolver.getproperty("license"),
            propertyresolver.getproperty("licenseurl"));
    }
}

after getting swagger to work, i discovered that endpoints published with @repositoryrestresource aren't picked up by swagger. there is an open issue for spring data support in the swagger-springmvc project.

liquibase integration

i configured this project to use h2 in development and postgresql in production. i used spring profiles to do this and copied xml/yaml (for maven and application*.yml files) from a previously created jhipster project.

next, i needed to create a database. i decided to use liquibase to create tables, rather than hibernate's schema-export. i chose liquibase over flyway based of discussions in the jhipster project . to use liquibase with spring boot is dead simple: add the following dependency to pom.xml, then place changelog files in src/main/resources/db/changelog .

<dependency>
    <groupid>org.liquibase</groupid>
    <artifactid>liquibase-core</artifactid>
</dependency>

i started by using hibernate's schema-export and changing hibernate.ddl-auto to "create-drop" in application-dev.yml . i also commented out the liquibase-core dependency. then i setup a postgresql database and started the app with "mvn spring-boot:run -pprod".

i generated the liquibase changelog from an existing schema using the following command (after downloading and installing liquibase).

liquibase --driver=org.postgresql.driver --classpath="/users/mraible/.m2/repository/org/postgresql/postgresql/9.3-1102-jdbc41/postgresql-9.3-1102-jdbc41.jar:/users/mraible/snakeyaml-1.11.jar" --changelogfile=/users/mraible/dev/spring-app/src/main/resources/db/changelog/db.changelog-02.yaml --url="jdbc:postgresql://localhost:5432/mydb" --username=user --password=pass generatechangelog

i did find one bug - the generatechangelog command generates too many constraints in version 3.2.2 . i was able to fix this by manually editing the generated yaml file.

tip: if you want to drop all tables in your database to verify liquibase creation is working in postgesql, run the following commands:

psql -d mydb
drop schema public cascade;
create schema public;

after writing minimal code for spring data and configuring liquibase to create tables/relationships, i relaxed a bit, documented how everything worked and added a loggingfilter . the loggingfilter was handy for viewing api requests and responses.

@bean
public filterregistrationbean loggingfilter() {
    loggingfilter filter = new loggingfilter();
    filterregistrationbean registrationbean = new filterregistrationbean();
    registrationbean.setfilter(filter);
    registrationbean.seturlpatterns(arrays.aslist("/api/*"));
    return registrationbean;
}

accessing api with resttemplate

the final step i needed to do was figure out how to access my new and fancy api with resttemplate . at first, i thought it would be easy. then i realized that spring data produces a hal -compliant api, so its content is embedded inside an "_embedded" json key.

after much trial and error, i discovered i needed to create a resttemplate with hal and joda-time awareness.

@bean
public resttemplate resttemplate() {
    objectmapper mapper = new objectmapper();
    mapper.configure(deserializationfeature.fail_on_unknown_properties, false);
    mapper.registermodule(new jackson2halmodule());
    mapper.registermodule(new jodamodule());

    mappingjackson2httpmessageconverter converter = new mappingjackson2httpmessageconverter();
    converter.setsupportedmediatypes(mediatype.parsemediatypes("application/hal+json"));
    converter.setobjectmapper(mapper);
    stringhttpmessageconverter stringconverter = new stringhttpmessageconverter();
    stringconverter.setsupportedmediatypes(mediatype.parsemediatypes("application/xml"));

    list<httpmessageconverter<?>> converters = new arraylist<>();
    converters.add(converter);
    converters.add(stringconverter);

    return new resttemplate(converters);
}

the jodamodule was provided by the following dependency:

<dependency>
    <groupid>com.fasterxml.jackson.datatype</groupid>
    <artifactid>jackson-datatype-joda</artifactid>
</dependency>

with the configuration complete, i was able to write a messagesapiitest integration test that posts a request and retrieves it using the api. the api was secured using basic authentication, so it took me a bit to figure out how to make that work with resttemplate. willie wheeler's basic authentication with spring resttemplate was a big help.

@runwith(springjunit4classrunner.class)
@contextconfiguration(classes = integrationtestconfig.class)
public class messagesapiitest {
    private final static log log = logfactory.getlog(messagesapiitest.class);
    @value("http://${app.host}/api/initiate")
    private string initiateapi;
    @value("http://${app.host}/api/messages")
    private string messagesapi;
    @value("${app.host}")
    private string host;
    @inject
    private resttemplate resttemplate;

    @before
    public void setup() throws exception {
        string request = new scanner(new classpathresource("sample-request.xml").getfile()).usedelimiter("\\z").next();

        responseentity<org.ncpdp.schema.transport.message> response = resttemplate.exchange(gettesturl(initiateapi),
                httpmethod.post, getbasicauthheaders(request), org.ncpdp.schema.transport.message.class,
                collections.emptymap());
        assertequals(httpstatus.ok, response.getstatuscode());
    }

    @test
    public void testgetmessages() {
        httpentity<string> request = getbasicauthheaders(null);
        responseentity<pagedresources<message>> result = resttemplate.exchange(gettesturl(messagesapi), httpmethod.get,
                request, new parameterizedtypereference<pagedresources<message>>() {});
        httpstatus status = result.getstatuscode();
        collection<message> messages = result.getbody().getcontent();

        log.debug("messages found: " + messages.size());
        assertequals(httpstatus.ok, status);
        for (message message : messages) {
            log.debug("message.id: " + message.getid());
            log.debug("message.datecreated: " + message.getdatecreated());
        }
    }

    private httpentity<string> getbasicauthheaders(string body) {
        string plaincreds = "user:pass";
        byte[] plaincredsbytes = plaincreds.getbytes();
        byte[] base64credsbytes = base64.encodebase64(plaincredsbytes);
        string base64creds = new string(base64credsbytes);
        httpheaders headers = new httpheaders();
        headers.add("authorization", "basic " + base64creds);
        headers.add("content-type", "application/xml");

        if (body == null) {
            return new httpentity<>(headers);
        } else {
            return new httpentity<>(body, headers);
        }
    }
}

to get spring data to populate the message id, i created a custom restconfig class to expose it. i learned how to do this from tommy ziegler .

/**
 * used to expose ids for resources.
 */
@configuration
public class restconfig extends repositoryrestmvcconfiguration {

    @override
    protected void configurerepositoryrestconfiguration(repositoryrestconfiguration config) {
        config.exposeidsfor(message.class);
        config.setbaseuri("/api");
    }
}

summary

this article explains how i built a rest api using jaxb, spring boot, spring data and liquibase. it was relatively easy to build, but required some tricks to access it with spring's resttemplate. figuring out how to customize jaxb's code generation was also essential to make things work.

i started developing the project with spring boot 1.1.7, but upgraded to 1.2.0.m2 after i found it supported log4j2 and configuring spring data rest's base uri in application.yml. when i handed the project off to my client last week, it was using 1.2.0.build-snapshot because of a bug when running in tomcat .

this was an enjoyable project to work on. i especially liked how easy spring data makes it to expose jpa entities in an api. spring boot made things easy to configure once again and liquibase seems like a nice tool for database migrations.

if someone asked me to develop a rest api on the jvm, which frameworks would i use? spring boot, spring data, jackson, joda-time, lombok and liquibase. these frameworks worked really well for me on this particular project.

Spring Framework Spring Data Spring Boot Data (computing) REST Web Protocols Database API

Published at DZone with permission of Matt Raible, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Spring Boot - How To Use Native SQL Queries | Restful Web Services
  • Develop a Spring Boot REST API in AWS: PART 4 (CodePipeline / CI/CD)
  • Building REST API Backend Easily With Ballerina Language
  • Composite Requests in Salesforce Are a Great Idea

Partner Resources

×

Comments

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
  • [email protected]

Let's be friends: