JavaScript Tutorial
This tutorial demonstrates using JavaScript within a Spring Boot application via GraalVM.
A practical use of JavaScript in a server-side pipeline is to re-use client-side validation logic on the server. The same rules that guard form inputs in the browser can run again at the HTTP boundary — written once, applied in both places.
The REST endpoint chains three steps: a Java step parses the request body, a JavaScript step validates it, then a Java step sends the response:
receive:
class: net.officefloor.tutorial.javascripthttpserver.ReceiveLogic
next: validate
validate:
resource: net/officefloor/tutorial/javascripthttpserver/Validate.js
procedure: JavaScript
method: validate
next: send
send:
class: net.officefloor.tutorial.javascripthttpserver.ResponseLogic
Maven dependency
Add GraalVM JavaScript support alongside the Spring Boot starter:
<dependency>
<groupId>net.officefloor.javascript</groupId>
<artifactId>officejavascript</artifactId>
</dependency>
Application class
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Receive step
The first step is a Java class that reads the @RequestBody and returns the parsed object as the parameter for the next step in the pipeline:
public class ReceiveLogic {
public Request receive(@RequestBody Request request) {
return request;
}
}
JavaScript validate step
The JavaScript function receives the Request as a pipeline parameter (param) from the previous step. It throws HttpException to abort the pipeline with the appropriate HTTP error status:
function validate(request) {
if (request.getId() < 1) {
throw new HttpException(HttpStatus.BAD_REQUEST, null, "Invalid identifier");
}
if (!request.getName()) {
throw new HttpException(HttpStatus.BAD_REQUEST, null, "Must provide name");
}
}
validate.officefloor = [
{ param : Request }
];
The officefloor meta-data array tells OfficeFloor the type of each function parameter. param means the value is passed from the previous step's return value rather than from a managed object source. For further parameter options see the JavaScript polyglot tests.
Send step
After JavaScript validation passes, the send step returns a successful response:
public class ResponseLogic {
public void send(ObjectResponse<Response> response) {
response.send(new Response("successful"));
}
}
Request and Response
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Request {
private int id;
private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Response {
private String message;
}
Note on scripting and threading
GraalVM script engines are created per-thread rather than shared. This avoids concurrency problems — all state is injected into the function — but it does mean memory scales with the number of threads. For validation at the socket boundary, that is bounded by the number of CPU cores, so the overhead is modest.
Testing
@SpringBootTest
@AutoConfigureMockMvc
public class JavaScriptHttpServerTest {
@Autowired
private MockMvc mvc;
@Autowired
private ObjectMapper mapper;
@Test
public void invalidIdentifier() throws Exception {
mvc.perform(get("/")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(new Request(-1, "Daniel"))))
.andExpect(status().isBadRequest())
.andExpect(content().string("Invalid identifier"));
}
@Test
public void invalidName() throws Exception {
mvc.perform(get("/")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(new Request(1, ""))))
.andExpect(status().isBadRequest())
.andExpect(content().string("Must provide name"));
}
@Test
public void validRequest() throws Exception {
mvc.perform(get("/")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(new Request(1, "Daniel")))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(mapper.writeValueAsString(new Response("successful"))));
}
}
Next
The next tutorial covers Cats Effect.

