All topics
Backend · Learning hub

Java notes for developers

Master Java with a curated set of 3 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Backend notes
Java

Core Java

Core Java OOP Fundamentals // Encapsulation — private fields, public getters/setters public class User { private String name; private String email; private int

Core Java

OOP Fundamentals

// Encapsulation — private fields, public getters/setters
public class User {
    private String name;
    private String email;
    private int age;

    public User(String name, String email, int age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }

    public String getName() { return name; }
    public void setName(String name) {
        if (name == null || name.isBlank()) throw new IllegalArgumentException("Name required");
        this.name = name;
    }

    @Override
    public String toString() {
        return String.format("User{name=%s, email=%s}", name, email);
    }
}

// Inheritance
public class Admin extends User {
    private String role;

    public Admin(String name, String email, String role) {
        super(name, email, 0);
        this.role = role;
    }

    @Override
    public String toString() {
        return super.toString() + String.format(", role=%s", role);
    }
}

// Interfaces (multiple allowed)
public interface Serializable { String serialize(); }
public interface Printable { void print(); }

public class Document implements Serializable, Printable {
    @Override
    public String serialize() { return "{...}"; }

    @Override
    public void print() { System.out.println(serialize()); }
}

// Abstract class
public abstract class Shape {
    abstract double area();
    abstract double perimeter();

    public String describe() {
        return String.format("Area: %.2f, Perimeter: %.2f", area(), perimeter());
    }
}

public class Circle extends Shape {
    private double radius;
    public Circle(double radius) { this.radius = radius; }

    @Override
    double area() { return Math.PI * radius * radius; }

    @Override
    double perimeter() { return 2 * Math.PI * radius; }
}

Collections & Generics

import java.util.*;
import java.util.stream.*;

// List
List<String> list = new ArrayList<>();
list.add("Alice");
list.addAll(Arrays.asList("Bob", "Carol"));
List<String> immutable = List.of("a", "b", "c");

// Map
Map<String, Integer> map = new HashMap<>();
map.put("alice", 1);
map.getOrDefault("bob", 0);
map.putIfAbsent("carol", 3);

// LinkedHashMap preserves insertion order
// TreeMap sorts by key
// Concurrent: ConcurrentHashMap

// Set
Set<String> set = new HashSet<>(Arrays.asList("a", "b", "c"));
Set<String> sorted = new TreeSet<>(set);

// Generics
public class Pair<A, B> {
    private final A first;
    private final B second;

    public Pair(A first, B second) {
        this.first = first;
        this.second = second;
    }
    public A getFirst() { return first; }
    public B getSecond() { return second; }
}

// Bounded wildcards
public double sumList(List<? extends Number> list) {
    return list.stream().mapToDouble(Number::doubleValue).sum();
}

// Streams API
List<String> names = List.of("Alice", "Bob", "Charlie", "David");

List<String> result = names.stream()
    .filter(n -> n.length() > 3)
    .map(String::toUpperCase)
    .sorted()
    .collect(Collectors.toList());

Map<Integer, List<String>> byLength = names.stream()
    .collect(Collectors.groupingBy(String::length));

Optional<String> first = names.stream()
    .filter(n -> n.startsWith("A"))
    .findFirst();

long count = names.stream().filter(n -> n.length() > 4).count();

Exceptions & Concurrency Basics

// Checked vs unchecked exceptions
// Checked: IOException, SQLException — must declare or catch
// Unchecked: RuntimeException, NullPointerException, IllegalArgumentException

try {
    Files.readString(Path.of("file.txt"));
} catch (IOException e) {
    logger.error("Failed to read file", e);
    throw new AppException("Config read failed", e);
} finally {
    cleanup();
}

// Try-with-resources
try (var conn = dataSource.getConnection();
     var stmt = conn.prepareStatement(sql)) {
    stmt.setString(1, userId);
    var rs = stmt.executeQuery();
    // auto-closed on exit
}

// Custom exception
public class ResourceNotFoundException extends RuntimeException {
    private final String resourceType;
    private final String id;

    public ResourceNotFoundException(String resourceType, String id) {
        super(String.format("%s with id %s not found", resourceType, id));
        this.resourceType = resourceType;
        this.id = id;
    }
}

// Concurrency
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<String> future = executor.submit(() -> fetchData());
String result = future.get(5, TimeUnit.SECONDS);
executor.shutdown();

// CompletableFuture — async chains
CompletableFuture.supplyAsync(() -> fetchUser(id))
    .thenApply(user -> enrichUser(user))
    .thenAccept(user -> cache.put(user.getId(), user))
    .exceptionally(e -> { logger.error("Failed", e); return null; });
Java

Spring Boot

Spring Boot Controllers & Services // REST Controller @RestController @RequestMapping("/api/users") @RequiredArgsConstructor public class UserController { priva

Spring Boot

Controllers & Services

// REST Controller
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @GetMapping
    public ResponseEntity<Page<UserDto>> getUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size,
            @RequestParam(required = false) String search) {
        var pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
        return ResponseEntity.ok(userService.findAll(pageable, search));
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
        return ResponseEntity.ok(userService.findById(id));
    }

    @PostMapping
    public ResponseEntity<UserDto> createUser(@Valid @RequestBody CreateUserRequest request) {
        var user = userService.create(request);
        return ResponseEntity.created(URI.create("/api/users/" + user.getId())).body(user);
    }

    @PatchMapping("/{id}")
    public ResponseEntity<UserDto> updateUser(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        return ResponseEntity.ok(userService.update(id, request));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

// Service
@Service
@Transactional
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final UserMapper userMapper;

    @Transactional(readOnly = true)
    public Page<UserDto> findAll(Pageable pageable, String search) {
        var page = search != null
            ? userRepository.findByNameContainingIgnoreCase(search, pageable)
            : userRepository.findAll(pageable);
        return page.map(userMapper::toDto);
    }

    @Transactional(readOnly = true)
    public UserDto findById(Long id) {
        return userRepository.findById(id)
            .map(userMapper::toDto)
            .orElseThrow(() -> new ResourceNotFoundException("User", id.toString()));
    }

    public UserDto create(CreateUserRequest request) {
        if (userRepository.existsByEmail(request.getEmail())) {
            throw new ConflictException("Email already registered");
        }
        var user = userMapper.toEntity(request);
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        return userMapper.toDto(userRepository.save(user));
    }
}

JPA Entity & Repository

@Entity
@Table(name = "users", indexes = {
    @Index(columnList = "email", unique = true),
    @Index(columnList = "created_at")
})
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    @JsonIgnore
    private String password;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Role role = Role.USER;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Post> posts = new ArrayList<>();

    @CreationTimestamp
    @Column(updatable = false)
    private Instant createdAt;

    @UpdateTimestamp
    private Instant updatedAt;
}

// Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
    boolean existsByEmail(String email);
    Page<User> findByNameContainingIgnoreCase(String name, Pageable pageable);

    @Query("SELECT u FROM User u WHERE u.role = :role AND u.createdAt > :since")
    List<User> findActiveUsersByRole(@Param("role") Role role, @Param("since") Instant since);
}

Global Exception Handler & Validation

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(new ErrorResponse(ex.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ValidationErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors()
            .forEach(e -> errors.put(e.getField(), e.getDefaultMessage()));
        return ResponseEntity.badRequest().body(new ValidationErrorResponse(errors));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
        log.error("Unexpected error", ex);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(new ErrorResponse("Internal server error"));
    }
}

// DTO with validation
public record CreateUserRequest(
    @Email @NotBlank String email,
    @NotBlank @Size(min = 2, max = 100) String name,
    @NotBlank @Size(min = 8) String password
) {}
Java

Interview Questions

Java & Spring Boot Interview Questions Q: What is the difference between == and equals() in Java? == compares object references (memory address) for objects, or

Java & Spring Boot Interview Questions

Q: What is the difference between == and equals() in Java?

== compares object references (memory address) for objects, or values for primitives. equals() compares object content (overridden in String, Integer, etc. to compare values). Always use equals() to compare String values. Use == only for reference equality or primitives.

Q: What is the difference between ArrayList and LinkedList?

ArrayList uses a dynamic array — O(1) random access, O(n) insertion/deletion in the middle. LinkedList uses a doubly-linked list — O(1) insertion/deletion at head/tail, O(n) random access. In practice, ArrayList is almost always faster due to cache locality. Use LinkedList only when you frequently insert/delete at both ends.

Q: What is Spring Dependency Injection?

Spring IoC container manages the lifecycle of beans (@Component, @Service, @Repository, @Controller). It injects dependencies via constructor injection (preferred), setter injection, or field injection (@Autowired). Constructor injection is preferred because it makes dependencies explicit, enables immutability (@RequiredArgsConstructor), and simplifies testing.

Q: What is @Transactional?

Marks a method or class as transactional — Spring wraps it in a database transaction. If the method completes normally, the transaction commits. If a RuntimeException is thrown, it rolls back. Use readOnly=true for read-only queries (performance hint). Propagation controls what happens when transactional methods call each other (REQUIRED, REQUIRES_NEW, NESTED, etc.).

Q: What is the JVM and how does garbage collection work?

The JVM executes Java bytecode. Garbage collection automatically reclaims memory for unreachable objects. The heap is divided into generations: Young (new objects, GC is frequent/fast), Old/Tenured (long-lived objects, less frequent). G1GC (default Java 9+) divides the heap into regions and tries to meet pause-time goals. Avoid premature optimization — tune GC only when profiling shows it's a bottleneck.

Keep your Java knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever