In this technical post we will take a look at how to produce XML using JAXB Jakarta EE implementation. Java Architecture for XML Binding (JAXB) provides an API and tools that automate the mapping between XML documents and Java objects. NOTE: If you need to know what tools you need to have installed in your computer in order to create a Spring Boot basic project, please refer my previous post: Spring Boot. Let’s start creating a new Spring Webflux with Lombok as dependency:
spring init --dependencies=webflux,lombok --build=gradle --language=java spring-webflux-jaxb
Here is the complete build.gradle
file generated:
plugins {
id 'org.springframework.boot' version '2.2.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.jos.dem.spring.webflux.jaxb'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'io.projectreactor:reactor-test'
}
test {
useJUnitPlatform()
}
Then add JAXB api and runtime dependencies:
implementation "javax.xml.bind:jaxb-api"
implementation "org.glassfish.jaxb:jaxb-runtime"
Let’s start adding a controller to retreive an XML
package com.jos.dem.spring.webflux.jaxb.controller;
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import com.jos.dem.spring.webflux.jaxb.model.Person;
import com.jos.dem.spring.webflux.jaxb.repository.PersonRepository;
@RestController
public class PersonController {
@Autowired
private PersonRepository personRepository;
private Logger log = LoggerFactory.getLogger(this.getClass());
@GetMapping(value = "/", produces = APPLICATION_XML_VALUE)
public Mono<Person> index() {
log.info("Getting Person");
return personRepository.findOne("josdem");
}
}
That’s it, adding APPLICATION_XML_VALUE
as MediaType to the @GetMapping
annotation will do the magic :). Here is our Person
model:
package com.jos.dem.spring.webflux.jaxb.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
@Getter
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement
public class Person {
@XmlAttribute
private String nickname;
@XmlAttribute
private String firstName;
@XmlAttribute
private String lastName;
@XmlAttribute
private String address;
@XmlElement
private Device device;
}
Where:
@XmlRootElement
annotation associate a root element with ourPerson
class.@XmlAttribute
Will map to a XML field attribute.@XmlElement
Will map to a XML element.
Here is our Device
model:
package com.jos.dem.spring.webflux.jaxb.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlAttribute;
@Getter
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement
public class Device {
@XmlAttribute
private String name;
@XmlAttribute
private String os;
@XmlAttribute
private String model;
}
Finally, let’s test our controller with WebTestClient
package com.jos.dem.spring.webflux.jaxb;
import com.jos.dem.spring.webflux.jaxb.model.Person;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
@SpringBootTest(classes = DemoApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DemoApplicationTests {
@Autowired
private WebTestClient webClient;
@Test
@DisplayName("Should Get Person")
void shouldGetPerson() {
webClient.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(APPLICATION_XML_VALUE)
.expectBody(Person.class)
.value(person -> person.getFirstName(), equalTo("Jose"))
.value(person -> person.getLastName(), equalTo("Morales"))
.value(person -> person.getAddress(), equalTo("30 Frank Lloyd, Ann Arbor MI 48105"))
.value(person -> person.getDevice().getName(), equalTo("Pixel 3"))
.value(person -> person.getDevice().getOs(), equalTo("Android"))
.value(person -> person.getDevice().getModel(), equalTo("9 Pie"));
}
}
If you want to test this project from command line
curl -v http://localhost:8080 \
-H 'Content-Type: application/xml'
You should see an output similar to this:
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/xml
>
< HTTP/1.1 200 OK
< Content-Type: application/xml
< Content-Length: 222
<
* Connection #0 to host localhost left intact
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><person nickname="josdem" firstName="Jose" lastName="Morales" address="30 Frank Lloyd, Ann Arbor MI 48105"><device name="Pixel 3" os="Android" model="9 Pie"/></person>
To browse the project go here, to download the project:
git clone git@github.com:josdem/spring-webflux-jaxb.git
To run the project:
gradle bootRun
To test the project:
gradle test