Senior Frontend Interview Guide, Part 15: Java and Spring Boot for Frontend Engineers
Most large banks run their backends on Java, and Spring Boot is the dominant framework. Job descriptions that mention "Java/Spring Boot environments" are not asking you to become a backend engineer; they want a frontend engineer who can read the backend code, run services locally, understand API…

This article is Part 15 of 18 in The Senior Frontend Engineer Interview Guide, and includes 18 interview questions with model answers.
Most large banks run their backends on Java, and Spring Boot is the dominant framework. Job descriptions that mention “Java/Spring Boot environments” are not asking you to become a backend engineer; they want a frontend engineer who can read the backend code, run services locally, understand API contracts and errors, debug across the stack, and collaborate effectively with backend teams. This chapter gives you a working knowledge of Java and Spring Boot from that perspective.
Java Essentials for JavaScript Developers
| Concept | JavaScript/TypeScript | Java |
|---|---|---|
| Typing | Dynamic (TypeScript adds static types) | Static, nominal, compiled to bytecode |
| Runtime | Browser / Node (single-threaded event loop) | JVM (multi-threaded) |
| Modules | ES modules | Packages and classes; JPMS modules |
| Package manager | npm / pnpm | Maven or Gradle |
| Null safety | undefined, null, optional chaining |
null, Optional<T> |
| Data classes | Objects, interfaces | Classes, records (Java 16+) |
| Async | Promises, async/await | Threads, CompletableFuture, reactive (WebFlux), virtual threads (Java 21) |
| Collections | Arrays, Map, Set | List, Map, Set interfaces with implementations |
| Functional | First-class functions | Lambdas, functional interfaces, Streams API |
Long-term support (LTS) versions are the ones enterprises run: Java 17 and Java 21 are the most common today, and Java 25 is the newest LTS. Spring Boot 3.x requires Java 17 or later.
Reading Java Code
package com.bank.payments.api;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
public record TransferRequest(
String fromAccountId,
String toAccountId,
BigDecimal amount,
String currency,
LocalDate scheduledDate,
String memo
) {}
public List<String> activePayeeNames(List<Payee> payees) {
return payees.stream()
.filter(Payee::isActive)
.map(Payee::name)
.sorted()
.toList();
}
- A record is an immutable data carrier with automatically generated constructor, accessors,
equals,hashCode, andtoString, much like a TypeScript type for a DTO. BigDecimalis Java’s arbitrary-precision decimal type, used for money. It is serialized to JSON as a number by default; many banks configure it to serialize as a string to avoid JavaScript precision loss, which is worth agreeing on with the backend team.- Streams resemble JavaScript array methods:
filter,map,reduce,sorted,collect. java.time(Instant,LocalDate,ZonedDateTime,OffsetDateTime) separates instants from calendar dates, mirroring the distinctions from Chapter 11.
Key Language Concepts
- Classes, interfaces, inheritance, and generics (
List<Payment>) are central. Generics are erased at runtime. - Exceptions: checked exceptions must be declared or caught; unchecked (
RuntimeException) need not be. Spring translates exceptions into HTTP responses. - Annotations (
@RestController,@Valid) are metadata processed by frameworks; Spring relies on them heavily. Optional<T>expresses a possibly absent value, comparable toT | undefined.- Concurrency: the JVM runs many threads. Spring MVC traditionally uses one thread per request; Java 21 virtual threads make that model scale much further.
Build Tools: Maven and Gradle
Maven (pom.xml) and Gradle (build.gradle or build.gradle.kts) manage dependencies, build, test, and package applications, much like package.json with npm scripts.
| Task | Maven | Gradle |
|---|---|---|
| Run the app | ./mvnw spring-boot:run |
./gradlew bootRun |
| Run tests | ./mvnw test |
./gradlew test |
| Build a JAR | ./mvnw package |
./gradlew bootJar |
| Show dependency tree | ./mvnw dependency:tree |
./gradlew dependencies |
The mvnw and gradlew wrapper scripts download the correct tool version, similar to using npx with a pinned version. Running backend services locally (often via Docker Compose with databases and message brokers) is a common task for full-stack debugging.
Spring Framework and Spring Boot
Spring Framework provides dependency injection (inversion of control), web frameworks, data access, security, and more. Spring Boot adds opinionated auto-configuration, embedded servers (Tomcat by default), starter dependencies, externalized configuration, and production features (Actuator), so a service can be started with minimal setup.
Dependency Injection
Spring creates and wires objects called beans. Classes annotated with @Component, @Service, @Repository, @Controller, or @Configuration are discovered and managed. Dependencies are injected, preferably through constructors:
@Service
public class TransferService {
private final AccountRepository accounts;
private final PaymentGateway gateway;
public TransferService(AccountRepository accounts, PaymentGateway gateway) {
this.accounts = accounts;
this.gateway = gateway;
}
}
This is the same idea as passing an API client into a React provider instead of importing a global: it decouples code and makes testing easy.
Layered Architecture
A typical Spring Boot service:
controller -> service -> repository -> database
(HTTP) (business) (data access)
DTOs domain entities
- Controllers handle HTTP: routing, request parsing, validation, and response mapping.
- Services hold business logic and transactions.
- Repositories access data (Spring Data JPA, JDBC, MongoDB).
- DTOs define API request and response shapes, separate from database entities.
Building REST APIs with Spring MVC
@RestController
@RequestMapping("/api/v1/transfers")
@Validated
public class TransferController {
private final TransferService service;
public TransferController(TransferService service) { this.service = service; }
@GetMapping("/{id}")
public TransferResponse get(@PathVariable String id, @AuthenticationPrincipal Jwt jwt) {
return service.findForCustomer(id, jwt.getSubject());
}
@GetMapping
public Page<TransferResponse> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "25") @Max(100) int size,
@AuthenticationPrincipal Jwt jwt) {
return service.list(jwt.getSubject(), PageRequest.of(page, size));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public TransferResponse create(
@RequestHeader("Idempotency-Key") String idempotencyKey,
@Valid @RequestBody CreateTransferRequest request,
@AuthenticationPrincipal Jwt jwt) {
return service.create(jwt.getSubject(), idempotencyKey, request);
}
}
| Annotation | Purpose | Frontend equivalent |
|---|---|---|
@RestController |
Class handles HTTP and returns JSON | An Express router |
@GetMapping, @PostMapping, etc. |
Map methods and paths | router.get(...) |
@PathVariable |
URL path segment | /transfers/:id |
@RequestParam |
Query parameter | ?page=0 |
@RequestBody |
JSON body deserialized to an object | req.body |
@RequestHeader |
HTTP header | req.headers[...] |
@Valid |
Trigger Bean Validation | Zod schema validation |
JSON serialization is handled by Jackson. Field names follow Java property names (usually camelCase), and configuration controls date formats (ISO 8601 by default with JavaTimeModule), null handling, and unknown properties.
Validation
public record CreateTransferRequest(
@NotBlank String fromAccountId,
@NotBlank String toAccountId,
@NotNull @DecimalMin(value = "0.01") @Digits(integer = 9, fraction = 2) BigDecimal amount,
@NotBlank @Size(min = 3, max = 3) String currency,
@FutureOrPresent LocalDate scheduledDate,
@Size(max = 140) String memo
) {}
When validation fails, Spring throws MethodArgumentNotValidException, which is typically translated into a 400 or 422 response with field errors. Aligning client-side validation messages with these server-side constraints (same limits, same field names) improves user experience; generating both from a shared OpenAPI schema keeps them consistent.
Error Handling with Problem Details
Spring Boot 3 supports RFC 9457 Problem Details through ProblemDetail and @RestControllerAdvice:
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(InsufficientFundsException.class)
public ProblemDetail insufficientFunds(InsufficientFundsException ex) {
ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.UNPROCESSABLE_ENTITY, ex.getMessage());
pd.setTitle("Insufficient funds");
pd.setType(URI.create("https://api.bank.example/problems/insufficient-funds"));
pd.setProperty("code", "INSUFFICIENT_FUNDS");
return pd;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ProblemDetail validation(MethodArgumentNotValidException ex) {
ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
pd.setTitle("Validation failed");
pd.setProperty("errors", ex.getFieldErrors().stream()
.map(fe -> Map.of("field", fe.getField(), "code", fe.getCode()))
.toList());
return pd;
}
}
The response is application/problem+json with type, title, status, detail, instance, and custom properties, which the frontend error normalizer from Chapter 8 consumes.
Spring Security
Spring Security handles authentication and authorization through a chain of servlet filters.
Resource Server with JWT
Most APIs consumed by SPAs are configured as OAuth 2.0 resource servers that validate JWT access tokens:
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/api/v1/public/**").permitAll()
.requestMatchers("/api/v1/wires/**").hasAuthority("SCOPE_payments.write")
.anyRequest().authenticated())
.oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()))
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable()) // acceptable only for stateless bearer-token APIs
.cors(Customizer.withDefaults());
return http.build();
}
}
# application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://login.bank.example/oauth2/default
The issuer URI lets Spring discover the identity provider’s signing keys (JWKS) and validate the token’s signature, issuer, and expiry. Method-level rules (@PreAuthorize("hasAuthority('SCOPE_payments.write')")) add finer authorization.
CSRF and Cookie-Based Sessions
For a BFF using session cookies, CSRF protection must stay enabled. Spring Security’s CookieCsrfTokenRepository writes an XSRF-TOKEN cookie that Axios automatically reads and returns in the X-XSRF-TOKEN header (Chapter 8), which is why the default names match.
CORS
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://app.bank.example"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type", "Idempotency-Key", "X-Request-ID"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
When a frontend engineer sees a CORS error, this configuration (or the API gateway’s equivalent) is where the fix belongs. Note that custom headers like Idempotency-Key must be explicitly allowed, a frequent cause of failed preflights.
Data Access with Spring Data
@Entity
@Table(name = "transfers")
public class Transfer {
@Id private UUID id;
private String customerId;
@Column(precision = 19, scale = 4) private BigDecimal amount;
@Enumerated(EnumType.STRING) private TransferStatus status;
private Instant createdAt;
@Version private long version; // optimistic locking
// getters, setters
}
public interface TransferRepository extends JpaRepository<Transfer, UUID> {
Page<Transfer> findByCustomerIdOrderByCreatedAtDesc(String customerId, Pageable pageable);
Optional<Transfer> findByCustomerIdAndIdempotencyKey(String customerId, String key);
}
Spring Data derives queries from method names, supports pagination (Page, Pageable), and integrates with transactions (@Transactional). The @Version field enables optimistic locking: concurrent updates to the same row fail with a conflict, which the API can surface as 409 Conflict, and the UI should handle by reloading and asking the user to retry.
Pagination Response Shape
Returning a Spring Page directly produces JSON with content, totalElements, totalPages, number, and size (Spring Data 3.3 recommends wrapping it in a stable DTO via PagedModel). Frontend engineers should agree on a stable pagination contract, especially when moving to cursor pagination for large transaction histories.
Configuration and Profiles
Spring Boot reads configuration from application.yml/application.properties, environment variables, and command-line arguments, with profiles for environments (application-dev.yml, application-prod.yml). Activate with SPRING_PROFILES_ACTIVE=dev. Secrets come from vaults or environment variables, never from source control.
Spring Boot Actuator and Observability
Actuator exposes operational endpoints: /actuator/health (liveness and readiness probes for Kubernetes), /actuator/info, and /actuator/metrics//actuator/prometheus. Micrometer provides metrics and tracing; with OpenTelemetry, a traceparent header sent by the frontend links browser spans to backend spans, giving end-to-end traces of a user action. Expose only the endpoints needed and secure them.
API Documentation and Contracts
springdoc-openapi generates an OpenAPI 3 specification from controllers and annotations, served at /v3/api-docs with Swagger UI at /swagger-ui.html. This specification is the contract between frontend and backend:
- Generate TypeScript types and clients from it (openapi-typescript, Orval).
- Detect breaking changes in CI by diffing specifications (oasdiff, openapi-diff).
- Use consumer-driven contract tests (Pact or Spring Cloud Contract) so the frontend’s expectations are verified against the backend’s build.
- Use the spec to drive MSW handlers for frontend tests.
Reactive Spring (WebFlux) Briefly
Spring WebFlux is a non-blocking, reactive alternative to Spring MVC using Project Reactor (Mono<T> for zero-or-one values, Flux<T> for streams). It suits high-concurrency streaming use cases, such as Server-Sent Events for payment status updates:
@GetMapping(value = "/api/v1/payments/{id}/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<PaymentEvent> events(@PathVariable String id) {
return paymentEvents.streamFor(id);
}
The browser consumes this with EventSource. With Java 21 virtual threads, many teams stay with Spring MVC for simplicity even at high concurrency.
Microservices Patterns Relevant to Frontends
- API gateway (Spring Cloud Gateway, Kong, Apigee) routes requests, enforces authentication, rate limits, and CORS.
- Backend-for-frontend services, sometimes written in Spring Boot, sometimes in Node.js by the frontend team.
- Service discovery and configuration (Spring Cloud, Kubernetes).
- Resilience with circuit breakers, retries, and timeouts (Resilience4j); a downstream failure might return a partial response or 503 that the UI must handle.
- Asynchronous messaging with Kafka or RabbitMQ: a payment request may be accepted (202) and processed by downstream consumers; the UI polls or receives events.
- Distributed transactions and sagas: a multi-step payment may complete some steps and compensate others, producing intermediate statuses the UI must represent.
Collaborating with Backend Teams
Interviewers like concrete practices:
- Agree on API contracts first (OpenAPI design reviews) so frontend and backend can work in parallel, with the frontend using MSW mocks generated from the spec.
- Standardize error formats (Problem Details), pagination, date and money representations, and naming conventions across services.
- Share correlation IDs and use distributed tracing to debug issues across the stack.
- Read backend code and logs when debugging; reproduce issues with curl or HTTP files; run the service locally with Docker Compose.
- Propose BFF endpoints that shape data for the UI rather than making the frontend orchestrate many calls.
Interview Questions and Answers
Q1. What is Spring Boot, and how does it differ from the Spring Framework?
Spring Framework provides core capabilities like dependency injection, web MVC, data access, and security. Spring Boot builds on it with auto-configuration, starter dependencies, embedded servers, externalized configuration, and production-ready features like Actuator, so applications run with minimal setup.
Q2. What is dependency injection, and why is it useful?
Objects receive their dependencies from the container rather than creating them, usually through constructor parameters. It decouples components, enables swapping implementations (such as a mock payment gateway in tests), and centralizes lifecycle management. It is conceptually similar to providing services through React Context.
Q3. Walk through what happens when the frontend calls POST /api/v1/transfers.
The request passes through the servlet container and Spring Security’s filter chain (CORS handling, JWT validation, authorization rules), then the DispatcherServlet routes it to the controller method. Jackson deserializes the JSON body into a DTO, Bean Validation checks constraints, the controller calls the service, which applies business rules inside a transaction and uses repositories to persist data. The result is serialized to JSON with a 201 status, or an exception is mapped by a controller advice to a Problem Details response.
Q4. How would you debug a CORS error between a React app and a Spring Boot API?
Inspect the failed preflight in the Network panel: check the requested method and headers. Then review the Spring CORS configuration or gateway: allowed origins (exact, not wildcard with credentials), methods, headers (custom headers such as Authorization or Idempotency-Key must be listed), and credentials. Ensure Spring Security has CORS enabled so preflight requests are not rejected by authentication.
Q5. How does a Spring Boot resource server validate a JWT?
It uses the configured issuer URI to fetch the identity provider’s metadata and JSON Web Key Set, verifies the token signature with the matching key, and validates claims including issuer, expiry, and optionally audience. Scopes or roles from the token become granted authorities used in authorization rules.
Q6. Why is BigDecimal used for money in Java, and what should the frontend watch for?
It represents decimal numbers exactly with configurable scale and rounding, avoiding floating-point errors. If it is serialized as a JSON number, JavaScript parses it as a double, which can lose precision for large or highly precise values. Prefer serializing amounts as strings or integer minor units and parse them with decimal-safe code in the frontend.
Q7. What are DTOs, and why not return JPA entities directly?
Data Transfer Objects define the API’s request and response shapes. Returning entities couples the API to the database schema, risks exposing internal or sensitive fields, causes lazy-loading and serialization issues, and makes API evolution harder. DTOs (often records) provide a stable contract.
Q8. How should validation be shared between frontend and backend?
The backend is authoritative. Keep constraints consistent by deriving both from a shared contract such as OpenAPI with JSON Schema constraints, or by aligning messages and codes. The frontend validates for immediate feedback and maps server field errors (by field name and code) to localized messages.
Q9. What is optimistic locking, and how should the UI react to a conflict?
A concurrency strategy where each record has a version; an update succeeds only if the version has not changed since it was read, otherwise it fails (a 409 Conflict or 412 Precondition Failed). The UI should inform the user that the data changed, reload the latest version, and let them reapply their changes.
Q10. What does Spring Boot Actuator provide?
Operational endpoints for health (liveness and readiness), metrics, info, environment, and more, used by Kubernetes probes and monitoring systems. They should be exposed selectively and secured.
Q11. What is the difference between Spring MVC and Spring WebFlux?
Spring MVC uses a blocking, thread-per-request model on the Servlet API; with Java 21 virtual threads it scales well with simple code. WebFlux is non-blocking and reactive using Reactor’s Mono and Flux, suited to streaming and very high concurrency with non-blocking dependencies. Most CRUD business services use MVC.
Q12. How can the frontend and backend stay in sync on API contracts?
Publish an OpenAPI specification from the backend (springdoc), generate TypeScript types and clients from it, check for breaking changes in CI, run consumer-driven contract tests with Pact or Spring Cloud Contract, and review API designs collaboratively before implementation.
Q13. What is Problem Details, and why standardize on it?
RFC 9457 defines a JSON format for HTTP API errors with type, title, status, detail, instance, and extension fields. Standardizing lets the frontend handle errors generically across all services, map codes to localized messages, and display trace IDs for support.
Q14. Why might CSRF be disabled for some Spring APIs but not others?
Stateless APIs authenticated with bearer tokens in the Authorization header are not vulnerable to CSRF because browsers do not attach those tokens automatically, so CSRF protection is often disabled there. APIs that rely on session cookies, such as a BFF, must keep CSRF protection enabled, typically with a cookie-based CSRF token that the frontend echoes in a header.
Q15. How do you run a Spring Boot service locally to debug a frontend issue?
Clone the repository, ensure the right JDK version (often managed with SDKMAN), start dependencies with Docker Compose, run ./mvnw spring-boot:run or ./gradlew bootRun with a local profile, point the frontend’s dev proxy at it, and use logs, the IDE debugger, and Swagger UI to reproduce the issue.
Q16. What are virtual threads?
Lightweight threads introduced as a standard feature in Java 21 and managed by the JVM rather than the operating system. They let blocking, thread-per-request code handle very large numbers of concurrent requests efficiently. Spring Boot 3.2 and later can enable them with spring.threads.virtual.enabled=true.
Q17. How do microservices patterns affect frontend design?
Requests may fan out across services with partial failures, so UIs need graceful degradation and partial rendering. Asynchronous processing produces intermediate statuses. Gateways and BFFs shape APIs for the UI. Distributed tracing with correlation IDs is essential for debugging. Eventual consistency means data may briefly be stale after writes.
Q18. How would you expose real-time payment status from Spring Boot to React?
Use Server-Sent Events from a WebFlux or MVC SseEmitter endpoint, or WebSockets (STOMP over WebSocket in Spring), publishing events from the payment processing pipeline (often consumed from Kafka). The React app subscribes with EventSource, updates the query cache, handles reconnection, and falls back to polling.
Key Takeaways
- Read Java comfortably: records, streams,
BigDecimal,java.time, annotations, and exceptions. - Spring Boot services follow controller, service, repository layers with DTOs and dependency injection.
- Spring Security validates JWTs as a resource server; CORS and CSRF configuration live there.
- Problem Details, OpenAPI, and contract tests make the frontend-backend boundary reliable.
- Know enough to run, read, and debug backend services, and to collaborate on API design.
← Part 14: Financial Applications and Payment Systems · All chapters · Part 16: Relational and NoSQL Databases →







