0% found this document useful (0 votes)
3 views16 pages

Java For Spring

The document is a guide to learning Java specifically tailored for Spring development, focusing on concepts directly relevant to Spring's source code and application development. It covers essential Java features such as interfaces, generics, lambdas, and streams, emphasizing their importance in writing readable and maintainable Spring code. The guide also discusses object lifecycle management, error handling, and the appropriate use of collections in Spring applications.

Uploaded by

Zamil
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views16 pages

Java For Spring

The document is a guide to learning Java specifically tailored for Spring development, focusing on concepts directly relevant to Spring's source code and application development. It covers essential Java features such as interfaces, generics, lambdas, and streams, emphasizing their importance in writing readable and maintainable Spring code. The guide also discusses object lifecycle management, error handling, and the appropriate use of collections in Spring applications.

Uploaded by

Zamil
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Java for Spring An opinionated guide

Java for Spring


An opinionated guide built backwards from Spring's source code

Interfaces · Generics · Lambdas · Stream API · Optional · Annotations · CompletableFuture · Lombok

8 Layers · Spring Bridge sections · Real-world patterns

The philosophy behind this document

Most Java courses teach Java. This document teaches Java through the lens of what Spring Boot actually needs. Every
concept here was chosen because it appears directly in Spring's source code, in your application code, or in the mental
model Spring developers share. If something doesn't serve that goal, it isn't here. The order matters: we start with the
abstractions Spring is built on (interfaces, generics), then add the modern Java features that make Spring code readable
(lambdas, streams, optional), and finish with the tooling that defines the ecosystem (Maven, Lombok, annotations). By the
end, Spring won't feel like magic — it will feel like Java.

LAYER 1

CONTRACTS — Interfaces & Generics


Why first? Spring is built on interfaces. JpaRepository, ApplicationContext, BeanFactory — all interfaces. Generics make them type-safe.
Without these, Spring's source code is unreadable.

■ Interface — the contract


// An interface defines WHAT, not HOW
public interface UserRepository {
User findById(Long id);
List<User> findAll();
void save(User user);
}

// Implementation defines HOW


public class PostgresUserRepository implements UserRepository {
@Override
public User findById(Long id) {
// actual SQL query here
return ...
}
}

// Caller only knows the contract, not the implementation


UserRepository repo = new PostgresUserRepository();
// — Spring injects the right impl automatically

■ Spring wires implementations to interfaces automatically — this is Dependency Injection.

■ Default & Static methods in interfaces (Java 8+)


public interface Auditable {
LocalDateTime getCreatedAt();

// default — optional override, has implementation


default boolean isRecent() {
return getCreatedAt().isAfter([Link]().minusDays(7));

—1—
Java for Spring An opinionated guide

// static — utility, belongs to interface


static Auditable of(LocalDateTime time) { ... }
}

■ Generics — type-safe contracts


// Without generics — dangerous, requires cast
public interface Repository {
Object findById(Object id); // cast nightmare
}

// With generics — safe and expressive


public interface Repository<T, ID> {
T findById(ID id); // T resolved at compile time
List<T> findAll();
void save(T entity);
}

// Usage — T=User, ID=Long


public class UserRepo implements Repository<User, Long> {
@Override
public User findById(Long id) { ... }
}

■ Bounded type parameters


// T must be a Number (or subclass)
public <T extends Number> double sum(List<T> list) {
return [Link]().mapToDouble(Number::doubleValue).sum();
}

// Wildcard — unknown type


void printAll(List<?> items) {
[Link]([Link]::println);
}

// Upper bounded wildcard — read-only list of Numbers


double total(List<? extends Number> nums) { ... }

// Lower bounded wildcard — writable list of Numbers


void addNumbers(List<? super Integer> nums) { ... }

Type Param Pattern Meaning Spring Example


Any type Repository

T is a Foo or subclass CrudRepository

Unknown type (read-only) List in reflection APIs

Producer — read from it Collection

Consumer — write to it Comparator

■ JpaRepository — T=User entity, ID=Long primary key. Memorise this pattern.

SPRING BRIDGE — Interfaces in Spring


// You write the interface
public interface UserService {
UserDto getUser(Long id);
UserDto createUser(CreateUserRequest req);
}

// You write the implementation


@Service

—2—
Java for Spring An opinionated guide

public class UserServiceImpl implements UserService {


private final UserRepository repo;

// constructor injection — preferred over @Autowired field


public UserServiceImpl(UserRepository repo) {
[Link] = repo;
}

@Override
public UserDto getUser(Long id) {
return [Link](id)
.map(UserMapper::toDto)
.orElseThrow(() -> new UserNotFoundException(id));
}
}

// Spring wires UserRepository -> UserServiceImpl -> Controller


// You never call 'new' — Spring manages object lifecycle

—3—
Java for Spring An opinionated guide

LAYER 2

OBJECT LIFECYCLE — Construction, Immutability & Patterns


Spring creates objects for you (beans). Understanding how objects are constructed, copied, and made immutable is critical for writing safe,
testable Spring components.

■ Constructors & Immutability


// Mutable class — dangerous in concurrent Spring context
public class UserDto {
public String name;
public String email;
}

// Immutable class — thread-safe, Spring-friendly


public final class UserDto {
private final String name;
private final String email;

public UserDto(String name, String email) {


[Link] = name;
[Link] = email;
}

public String getName() { return name; }


public String getEmail() { return email; }

// 'wither' pattern — return new instance with changed field


public UserDto withEmail(String email) {
return new UserDto([Link], email);
}
}

■ Immutable objects = no synchronization needed = better performance in Spring.

■ Builder Pattern — for objects with many optional fields


public class HttpRequest {
private final String url;
private final String method;
private final Map<String,String> headers;
private final String body;
private final int timeout;

private HttpRequest(Builder b) {
[Link] = [Link];
[Link] = [Link];
[Link] = [Link]([Link]);
[Link] = [Link];
[Link] = [Link];
}

public static class Builder {


private String url;
private String method = "GET";
private Map<String,String> headers = new HashMap<>();
private String body;
private int timeout = 30;

public Builder url(String url) { [Link] = url; return this; }


public Builder method(String m) { [Link] = m; return this; }
public Builder header(String k,String v){ [Link](k,v); return this; }
public Builder body(String b) { [Link] = b; return this; }
public Builder timeout(int t) { [Link] = t; return this; }
public HttpRequest build() { return new HttpRequest(this); }

—4—
Java for Spring An opinionated guide

}
}

// Usage — readable, no positional argument confusion


HttpRequest req = new [Link]()
.url("[Link]
.method("POST")
.header("Content-Type", "application/json")
.body(json)
.timeout(60)
.build();

■ Lombok @Builder generates all this boilerplate — see Layer 8.

■ Records (Java 16+) — immutable data carriers


// Record = immutable class + constructor + getters + equals/hashCode/toString
public record UserDto(String name, String email, int age) {}

// Equivalent to ~40 lines of manual code above


UserDto dto = new UserDto("Alice", "alice@[Link]", 30);
[Link](); // accessor (not getName()!)
[Link]();

// Compact canonical constructor — add validation


public record UserDto(String name, String email, int age) {
public UserDto {
[Link](name, "name required");
[Link](email, "email required");
if (age < 0) throw new IllegalArgumentException("age >= 0");
}
}

■ Spring Boot 3+ supports Records as @RequestBody DTOs and @ConfigurationProperties.

■ Static Factory Methods — preferred over 'new'


public class Money {
private final BigDecimal amount;
private final Currency currency;

// Private constructor
private Money(BigDecimal amount, Currency currency) { ... }

// Named factory methods — intent is clear


public static Money of(BigDecimal amount, Currency currency) {
if ([Link]([Link]) < 0)
throw new IllegalArgumentException("Negative money");
return new Money(amount, currency);
}

public static Money zero(Currency currency) {


return new Money([Link], currency);
}
}

Money price = [Link](new BigDecimal("9.99"), [Link]);

■ You'll see this pattern everywhere in Spring: [Link](...), [Link](...)

—5—
Java for Spring An opinionated guide

LAYER 3

MODERN JAVA — Lambdas, Streams & Optional


This layer is non-negotiable for Spring. Spring Data returns Stream. Spring Security uses functional predicates. Every Repository method
returns Optional. Skip this and Spring code is unreadable.

■ Functional Interfaces — the foundation of lambdas


Interface Method In / Out Spring Use
Predicate test(T t) -> boolean T -> bool @PreAuthorize conditions

Function apply(T t) -> R T -> R .map() in streams

Consumer accept(T t) -> void T -> void forEach, event handlers

Supplier get() -> T () -> T Lazy bean initialization

BiFunction apply(T,U) -> R T,U -> R Merging, combining

UnaryOperator apply(T t) -> T T -> T Middleware chains

■ Lambda syntax
// Verbose anonymous class
Predicate<String> isLong = new Predicate<String>() {
@Override public boolean test(String s) { return [Link]() > 5; }
};

// Lambda — same thing, 1 line


Predicate<String> isLong = s -> [Link]() > 5;

// Multi-param
BiFunction<Integer,Integer,Integer> add = (a, b) -> a + b;

// Multi-line body
Function<String, String> clean = input -> {
String trimmed = [Link]();
return [Link]();
};

// Method reference — even cleaner


Function<String,Integer> parse = Integer::parseInt; // static
Function<String,String> upper = String::toUpperCase; // instance
Supplier<List<String>> newList = ArrayList::new; // constructor

■ Stream API — process collections declaratively


List<User> users = [Link](); // imagine this is your database result

// PIPELINE: source -> intermediate ops -> terminal op


List<String> adminEmails = [Link]() // 1. source
.filter(u -> [Link]() == [Link]) // 2. filter
.filter(u -> [Link]()) // 2. chain filters
.sorted([Link](User::getName)) // 2. sort
.map(User::getEmail) // 2. transform
.distinct() // 2. deduplicate
.collect([Link]()); // 3. terminal

// Other terminals
long count = [Link]().filter(User::isActive).count();
boolean any = [Link]().anyMatch(u -> [Link]() > 60);
boolean all = [Link]().allMatch(User::isVerified);
Optional<User> first = [Link]().filter(...).findFirst();

// Reducing
int totalAge = [Link]().mapToInt(User::getAge).sum();
double avgAge = [Link]().mapToInt(User::getAge).average().orElse(0);

—6—
Java for Spring An opinionated guide

// Grouping
Map<Role, List<User>> byRole = [Link]()
.collect([Link](User::getRole));

// Joining strings
String names = [Link]()
.map(User::getName)
.collect([Link](", "));

■ Spring Data Page and Slice wrap query results — call .stream() on .getContent().

■ Optional — the null eliminator


// The problem: NullPointerException
User user = [Link](id); // returns null if not found
String name = [Link](); // CRASH if user is null

// The solution: Optional<T>


Optional<User> opt = [Link](id); // never null

// 1. If present, do something
[Link](u -> sendWelcomeEmail([Link]()));

// 2. Map — transform if present


Optional<String> email = [Link](User::getEmail);

// 3. OrElse — default value


User user = [Link]([Link]);

// 4. OrElseThrow — exception if absent


User user = [Link](() -> new UserNotFoundException(id));

// 5. FlatMap — Optional inside Optional


Optional<Address> addr = [Link](User::getOptionalAddress);

// 6. Filter
Optional<User> admin = [Link](u -> [Link]() == [Link]);

// Creating Optional
[Link](value); // value must NOT be null
[Link](value); // ok if null
[Link](); // explicitly empty

■ Never call [Link]() without checking isPresent() first — defeats the purpose.
■ All Spring Data findById() methods return Optional. Chain .orElseThrow() directly.

LAYER 4

ERRORS & RESOURCES — Exceptions & try-with-resources


Spring's @ExceptionHandler is just Java exceptions with annotations. Understanding checked vs unchecked determines your API design.

■ Checked vs Unchecked — the most important distinction


Type Extends Must handle? When to use Spring uses
Rare — Spring
External resources: IO, DB,
Checked Exception Yes (compile) prefers
network
unchecked

Programming errors, domain @ResponseStatus


Unchecked RuntimeException No
violations exceptions

JVM failures: OutOfMemory,


Error Error Never catch Never touch
StackOverflow

■ Custom exceptions — the Spring way

—7—
Java for Spring An opinionated guide

// Base domain exception — unchecked


public class DomainException extends RuntimeException {
private final String errorCode;

public DomainException(String errorCode, String message) {


super(message);
[Link] = errorCode;
}

public DomainException(String errorCode, String message, Throwable cause) {


super(message, cause);
[Link] = errorCode;
}

public String getErrorCode() { return errorCode; }


}

// Specific exceptions extend base


public class UserNotFoundException extends DomainException {
public UserNotFoundException(Long id) {
super("USER_NOT_FOUND", "User not found: " + id);
}
}

public class EmailAlreadyExistsException extends DomainException {


public EmailAlreadyExistsException(String email) {
super("EMAIL_EXISTS", "Email already registered: " + email);
}
}

■ try-with-resources — always for IO


// AutoCloseable resources close automatically — even on exception
try (var conn = [Link]();
var stmt = [Link]("SELECT * FROM users WHERE id=?");
var reader = new BufferedReader(new FileReader("[Link]"))) {

[Link](1, userId);
ResultSet rs = [Link]();
// process rs...

} catch (SQLException e) {
throw new DomainException("DB_ERROR", "Query failed", e);
}
// conn, stmt, reader all closed here — no finally block needed

■ Any class implementing AutoCloseable works in try-with-resources.


■ Spring's @Transactional handles connection lifecycle — you rarely open connections manually.

■ Global exception handling in Spring (preview)


// This is pure Java with Spring annotations on top
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler([Link])
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(UserNotFoundException ex) {
return new ErrorResponse([Link](), [Link]());
}

@ExceptionHandler([Link])
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleDomain(DomainException ex) {
return new ErrorResponse([Link](), [Link]());
}
}

—8—
Java for Spring An opinionated guide

LAYER 5

COLLECTIONS — The Right Tool for the Right Job


Spring repositories return List, Set, Map, Page. Knowing when to use which collection avoids N+1 queries and performance bugs.

■ Collection hierarchy (what matters)


Interface Best Implementation Ordered? Duplicates? Null? Spring use
findAll(), request
List ArrayList Yes (index) Yes Yes
bodies

HashSet /
Set No / insert No One Unique tags, roles
LinkedHashSet

HashMap / Grouping, lookup


Map No / insert Keys no One
LinkedHashMap tables

Event queues, batch


Queue ArrayDeque FIFO Yes No
jobs

Undo stacks, browser


Deque ArrayDeque Both ends Yes No
history

■ Choosing the right List


// ArrayList — get O(1), add O(1) amortized, remove middle O(n)
// — use for: most cases, iteration, random access
List<User> users = new ArrayList<>();

// LinkedList — add/remove head O(1), get O(n)


// — use for: frequent insertions/deletions at front/back
Deque<Task> taskQueue = new LinkedList<>();

// Unmodifiable — prevent accidental mutation (Spring config)


List<String> allowed = [Link]("GET", "POST", "PUT"); // Java 9+
Map<String,String> config = [Link]("host","localhost","port","8080");

■ Map operations — what you'll actually use


Map<Long, User> cache = new HashMap<>();

// Put / get / remove


[Link]([Link](), user);
User u = [Link](id); // null if absent
User u = [Link](id, [Link]);

// Compute patterns — avoid double lookup


[Link](id, loadFromDb(id));
[Link](id, k -> loadFromDb(k)); // lambda, lazy
[Link](id, (k,v) -> [Link](now()));

// Merge — combine old and new value


[Link](word, 1, Integer::sum);

// Iteration
[Link]((id, user) -> [Link]("{}: {}", id, [Link]()));
[Link]().stream()
.filter(e -> [Link]().isActive())
.map([Link]::getValue)
.collect([Link]());

■ Spring's ApplicationContext is essentially a Map of beans.

LAYER 6

ANNOTATIONS & REFLECTION — How Spring Magic Works

—9—
Java for Spring An opinionated guide

Spring annotations are not magic. They are Java annotations that Spring's reflection-based framework reads at startup. Understanding this
demystifies @Autowired, @Transactional, and @RequestMapping entirely.

■ Creating custom annotations


import [Link].*;

// 1. Define the annotation


@Retention([Link]) // visible at runtime via reflection
@Target([Link]) // only on methods
public @interface LogExecutionTime {
String value() default ""; // optional description
}

// 2. Use it
public class UserService {
@LogExecutionTime("find user")
public User findById(Long id) { ... }
}

// 3. Read it via reflection (Spring does this for you with AOP)
Method method = [Link]("findById", [Link]);
if ([Link]([Link])) {
LogExecutionTime ann = [Link]([Link]);
long start = [Link]();
// invoke method...
[Link]("{} took {}ms", [Link](), ([Link]()-start)/1_000_000);
}

■ Reflection basics — what Spring does internally


Class<?> clazz = [Link];

// Inspect fields
for (Field field : [Link]()) {
[Link](true); // access private fields
[Link]([Link]() + ": " + [Link]());
}

// Create instance dynamically


Object instance = [Link]().newInstance();

// Invoke method dynamically


Method m = [Link]("getName");
String name = (String) [Link](instance);

// Inspect annotations on a class


if ([Link]([Link])) {
Entity ann = [Link]([Link]);
String tableName = [Link]();
}

■ Spring scans your classpath at startup, reads all annotations via reflection, and builds the bean context.
■ Reflection is slow — Spring caches the results. Don't use it in hot paths yourself.
@Retention Visibility
SOURCE Compile only — discarded in .class file (@Override, @SuppressWarnings)

CLASS In .class file — not at runtime (default)

RUNTIME At runtime via reflection — required for Spring annotations

— 10 —
Java for Spring An opinionated guide

LAYER 7

CONCURRENCY — Threading, Async & CompletableFuture


Spring handles many requests simultaneously. Understanding thread safety prevents data corruption. CompletableFuture is how modern
Spring apps do async operations without blocking threads.

■ Thread safety — the core problem


// PROBLEM — shared mutable state
public class Counter {
private int count = 0; // shared between threads

// NOT thread-safe — read-modify-write is 3 operations


public void increment() { count++; }
}

// SOLUTION 1: synchronized method


public synchronized void increment() { count++; }

// SOLUTION 2: AtomicInteger — lock-free, faster


private AtomicInteger count = new AtomicInteger(0);
public void increment() { [Link](); }

// SOLUTION 3: avoid shared state entirely (best)


// Spring beans are stateless — move state to DB/Redis

■ Spring beans are singletons — never store request-scoped data in instance fields!

■ CompletableFuture — modern async


import [Link];

// 1. Run async task


CompletableFuture<User> future = [Link](() -> {
return [Link](id); // runs in ForkJoinPool
});

// 2. Chain transformations (non-blocking)


CompletableFuture<UserDto> dtoFuture = future
.thenApply(UserMapper::toDto) // transform result
.thenApply(dto -> [Link]("USER"));

// 3. Combine two futures


CompletableFuture<User> userFuture = fetchUser(id);
CompletableFuture<Profile> proFuture = fetchProfile(id);
CompletableFuture<FullUser> combined =
[Link](proFuture, FullUser::new);

// 4. Run all and wait


[Link](f1, f2, f3).join();

// 5. Exception handling
future
.exceptionally(ex -> [Link])
.whenComplete((result, ex) -> [Link]("Done: {}", result));

// 6. Get result (blocking — avoid in reactive apps)


User user = [Link](5, [Link]);

■ Annotate method with @Async — Spring wraps it in CompletableFuture automatically.

■ Executor Service — control your thread pools


// Don't use raw Thread — use ExecutorService
ExecutorService pool = [Link](4);

Future<String> f = [Link](() -> fetchFromApi());

— 11 —
Java for Spring An opinionated guide

String result = [Link](); // blocks

[Link](); // graceful shutdown


[Link](30, [Link]);

■ Configure Spring's TaskExecutor in [Link]: [Link]-size=4

LAYER 8

TOOLING — Maven, Lombok & Project Structure


Before you write a single line of Spring, you need to understand the project structure. Spring Initializr generates this — understanding it
means you can debug dependency issues and add features confidently.

■ Maven — [Link] structure


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="[Link]
<modelVersion>4.0.0</modelVersion>

<!-- Your project coordinates -->


<groupId>[Link]</groupId>
<artifactId>my-api</artifactId>
<version>0.0.1-SNAPSHOT</version>

<!-- Inherits Spring Boot's managed dependency versions -->


<parent>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>

<dependencies>
<!-- Web: @RestController, HTTP server -->
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Database: JPA + Hibernate -->


<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- Lombok: eliminates boilerplate -->


<dependency>
<groupId>[Link]</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

■ Lombok — eliminate boilerplate


Annotation Generates Replaces
@Getter All getters 10+ manual methods

@Setter All setters 10+ manual methods

@ToString toString() Manual toString

@EqualsAndHashCode equals() + hashCode() ~20 lines

Constructor with no
@NoArgsConstructor Boilerplate
args

— 12 —
Java for Spring An opinionated guide

Constructor with all


@AllArgsConstructor Boilerplate
fields

Constructor for final


@RequiredArgsConstructor Spring constructor DI
fields

@Getter+@Setter+@ToStri
@Data Entire DTO/Entity boilerplate
ng+@EH+@RA

@Builder Full builder pattern 60+ lines of builder code

private static final


@Slf4j Logger declaration
Logger log

// Without Lombok — 60 lines

// With Lombok — 8 lines


@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
private Long id;
private String name;
private String email;
private Role role;
}

// Usage
UserDto dto = [Link]()
.id(1L)
.name("Alice")
.email("alice@[Link]")
.role([Link])
.build();

@Service
@RequiredArgsConstructor // generates constructor for final fields
@Slf4j // injects log variable
public class UserService {
private final UserRepository userRepo; // injected via constructor
private final EmailService emailSvc;

public UserDto create(CreateUserRequest req) {


[Link]("Creating user: {}", [Link]());
// ...
}
}

■ Avoid @Data on @Entity classes — it can cause infinite loops via toString() with lazy relations.
■ Use @Getter @Setter separately on JPA entities for more control.

■ Spring Boot project structure


src/
main/
java/com/example/myapi/
[Link] // @SpringBootApplication entry point
controller/
[Link] // @RestController — HTTP layer
service/
[Link] // interface — contract
[Link] // @Service — business logic
repository/
[Link] // @Repository extends JpaRepository
domain/
[Link] // @Entity — database model
[Link] // @Data — transfer object
exception/
[Link] // custom exceptions

— 13 —
Java for Spring An opinionated guide

[Link] // @RestControllerAdvice
config/
[Link] // @Configuration
resources/
[Link] // database, server, logging config
test/
java/com/example/myapi/
[Link] // @SpringBootTest / @ExtendWith

■ Follow this structure from day one — Spring Boot's auto-configuration scans it.

— 14 —
Java for Spring An opinionated guide

SPRING BRIDGE — Everything Together — A Complete Spring Service


This is what a real Spring service looks like using every concept from this guide. Every line maps back to a layer above.
// LAYER 8: Lombok + LAYER 2: Immutable DTO with Builder
@Data @Builder @NoArgsConstructor @AllArgsConstructor
public class CreateUserRequest {
@NotBlank private String name;
@Email private String email;
}

// LAYER 1: Interface contract


public interface UserService {
UserDto createUser(CreateUserRequest req);
Optional<UserDto> findById(Long id); // LAYER 3: Optional
List<UserDto> findActiveAdmins(); // LAYER 5: List
}

// LAYER 8: Lombok | LAYER 1: Interface impl | LAYER 4: Exception


@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl implements UserService {
private final UserRepository repo; // LAYER 1: Interface
private final PasswordEncoder encoder;

@Override
@Transactional
public UserDto createUser(CreateUserRequest req) {
[Link]("Creating user: {}", [Link]());

// LAYER 4: Domain exception


if ([Link]([Link]()))
throw new EmailAlreadyExistsException([Link]());

// LAYER 2: Builder pattern


User user = [Link]()
.name([Link]())
.email([Link]())
.password([Link]("temp"))
.role([Link]) // LAYER 5: Enum
.createdAt([Link]()) // LAYER 3: [Link]
.build();

return [Link]([Link](user));
}

@Override
public Optional<UserDto> findById(Long id) {
// LAYER 3: Stream + map on Optional
return [Link](id).map(UserMapper::toDto);
}

@Override
public List<UserDto> findActiveAdmins() {
// LAYER 3: Stream API | LAYER 5: filter + map + collect
return [Link]().stream()
.filter(u -> [Link]() && [Link]() == [Link])
.map(UserMapper::toDto)
.sorted([Link](UserDto::getName))
.collect([Link]());
}
}

— 15 —
Java for Spring An opinionated guide

SPRING BRIDGE — What to Learn Next — in this order

# Topic Why now Resource


ApplicationContext, @Bean,
1 Spring Core + DI [Link]/guides
@Component

@SpringBootApplication,
2 Spring Boot basics [Link]/quickstart
auto-config, YAML

@RestController,
3 Spring MVC / REST Spring in Action (book)
@RequestMapping, DTOs

@Entity, @Repository, JPQL,


4 Spring Data JPA Hibernate docs
pagination

Spring Security Filter chain,


5 Baeldung tutorials
(JWT) UserDetailsService, tokens

@SpringBootTest, MockMvc,
6 Testing JUnit 5 docs
@MockBean

@Aspect, @Around, cross-cutting


7 Spring AOP Spring docs
concerns

Spring Reactive
8 WebFlux, Mono, Flux Project Reactor docs
(opt.)

— 16 —

You might also like