Spring REST Getting Started

This tutorial gets you from zero to a running REST endpoint. Add one dependency to your pom.xml, write one YAML file, and your endpoint is live. No controllers, no @RequestMapping, no Spring MVC configuration.

Tutorial Source

Maven dependency

The starter is published to Maven Central, so no additional repository configuration is needed. Adding a single dependency to pom.xml is all that is required:

		<dependency>
			<groupId>net.officefloor.springboot</groupId>
			<artifactId>officefloor-rest-spring-boot-4-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

Application class

The application entry point is a standard Spring Boot class with no OfficeFloor-specific code:

@SpringBootApplication
public class SpringRestGettingStartedApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringRestGettingStartedApplication.class, args);
	}
}

Your first endpoint

An endpoint is a YAML file placed under src/main/resources/officefloor/rest/. The file name encodes the HTTP method and the URL path:

officefloor/rest/
└── greeting.GET.yml     →  GET  /greeting

The YAML file names the Java class that handles the request:

service:
  class: net.officefloor.tutorial.springrestgettingstarted.GetGreetingLogic

The handler is a plain Java class with no framework annotations on the class itself:

public class GetGreetingLogic {

	public void service(GreetingService greetingService, ObjectResponse<GreetingResponse> response) {
		response.send(new GreetingResponse(greetingService.greet("World")));
	}
}

OfficeFloor registers every Spring bean in the application context as a managed object. GreetingService is a plain Spring @Service injected automatically by type into any service method parameter whose type matches:

@Service
public class GreetingService {

	public String greet(String name) {
		return "Hello, " + name + "!";
	}
}

ObjectResponse<T> serialises the object to JSON and writes it to the HTTP response. No @ResponseBody or @RestController is needed.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class GreetingResponse {

	private String message;
}

Path parameter

A path variable in the URL is expressed by a curly-brace file or directory name:

officefloor/rest/
└── greeting/
    └── {name}.GET.yml   →  GET  /greeting/{name}
service:
  class: net.officefloor.tutorial.springrestgettingstarted.GetNamedGreetingLogic

The handler receives the path variable as a @PathVariable parameter. Always use the name = attribute form. The shorthand @PathVariable("name") sets the value attribute; OfficeFloor resolves arguments from raw Java reflection where @AliasFor synthesis is not applied, so the shorthand silently produces an empty name and the binding fails:

public class GetNamedGreetingLogic {

	public void service(
			@PathVariable(name = "name") String name,
			GreetingService greetingService,
			ObjectResponse<GreetingResponse> response) {
		response.send(new GreetingResponse(greetingService.greet(name)));
	}
}

Testing

The application is a standard Spring Boot application, so tests use MockMvc:

@SpringBootTest
@AutoConfigureMockMvc
public class SpringRestGettingStartedTest {

	@Autowired
	private MockMvc mvc;

	@Autowired
	private ObjectMapper mapper;

	@Test
	public void getGreeting() throws Exception {
		mvc.perform(MockMvcRequestBuilders.get("/greeting")
				.accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk())
				.andExpect(content().json(mapper.writeValueAsString(new GreetingResponse("Hello, World!"))));
	}

	@Test
	public void getNamedGreeting() throws Exception {
		mvc.perform(MockMvcRequestBuilders.get("/greeting/OfficeFloor")
				.accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk())
				.andExpect(content().json(mapper.writeValueAsString(new GreetingResponse("Hello, OfficeFloor!"))));
	}
}

Running against a real server

Because the application is standard Spring Boot, it can be run directly:

mvn spring-boot:run

With the server running, the endpoints respond to plain HTTP:

curl http://localhost:8080/greeting
{"message":"Hello, World!"}

curl http://localhost:8080/greeting/OfficeFloor
{"message":"Hello, OfficeFloor!"}

The integration test does the same thing programmatically. @SpringBootTest(webEnvironment = RANDOM_PORT) starts an embedded server on a random port and TestRestTemplate makes real HTTP calls:

@AutoConfigureTestRestTemplate
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringRestGettingStartedRealServerTest {

	@Autowired
	private TestRestTemplate restTemplate;

	@Test
	public void getGreeting() {
		ResponseEntity<GreetingResponse> response = restTemplate.getForEntity(
				"/greeting", GreetingResponse.class);
		assertEquals(HttpStatus.OK, response.getStatusCode());
		assertEquals(new GreetingResponse("Hello, World!"), response.getBody());
	}

	@Test
	public void getNamedGreeting() {
		ResponseEntity<GreetingResponse> response = restTemplate.getForEntity(
				"/greeting/OfficeFloor", GreetingResponse.class);
		assertEquals(HttpStatus.OK, response.getStatusCode());
		assertEquals(new GreetingResponse("Hello, OfficeFloor!"), response.getBody());
	}
}

Next

The Spring REST tutorial covers YAML naming conventions in depth, multi-method service classes, multiple path variables, and custom response headers via ResponseEntity.