Python

🍃 Spring Boot: REST API Modern

Tutorial lengkap Spring Boot — dependency injection, REST controller, JPA database, security basics, dan quiz interaktif dengan contoh kode praktis

1. Pengenalan Spring Boot

Spring Boot adalah framework Java yang memudahkan pembuatan aplikasi berbasis Spring. Dengan pendekatan convention over configuration dan auto-configuration, Spring Boot memungkinkan developer membuat production-ready REST API dalam hitungan menit.

Spring Boot dibangun di atas Spring Framework dan menyediakan embedded server (Tomcat), auto-configuration, production-ready features (health check, metrics), dan starter dependencies yang mempercepat development.

Mengapa Spring Boot?

Keunggulan Penjelasan
Auto-ConfigurationSpring otomatis mendeteksi dan mengkonfigurasi komponen
Embedded ServerTomcat/Jetty built-in, tidak perlu deploy ke server eksternal
Starter DependenciesDependency grouping yang sudah dikurasi (spring-boot-starter-web, dll)
Production-ReadyActuator untuk health check, metrics, monitoring
Ekosistem LuasSpring Data, Spring Security, Spring Cloud, dan banyak lagi
Komunitas BesarDokumentasi lengkap, banyak tutorial, Stack Overflow aktif

Arsitektur Spring Boot

Diagram: Arsitektur REST API dengan Spring Boot
┌─────────────────────────────────────────────────────────────┐
│                  SPRING BOOT ARCHITECTURE                    │
│                                                             │
│  Client (Browser/App)                                       │
│       │                                                     │
│       ▼  HTTP Request                                       │
│  ┌──────────────────────┐                                   │
│  │   Controller Layer   │  ← @RestController                │
│  │   (REST endpoints)   │    Terima request, kirim response │
│  └──────────┬───────────┘                                   │
│             ▼                                               │
│  ┌──────────────────────┐                                   │
│  │   Service Layer      │  ← @Service                       │
│  │   (Business Logic)   │    Logika bisnis, validasi        │
│  └──────────┬───────────┘                                   │
│             ▼                                               │
│  ┌──────────────────────┐                                   │
│  │   Repository Layer   │  ← @Repository (JPA)              │
│  │   (Data Access)      │    Query database                 │
│  └──────────┬───────────┘                                   │
│             ▼                                               │
│  ┌──────────────────────┐                                   │
│  │   Database           │  (MySQL, PostgreSQL, H2)          │
│  └──────────────────────┘                                   │
└─────────────────────────────────────────────────────────────┘

2. Setup Proyek Baru

Cara termudah untuk membuat proyek Spring Boot adalah menggunakan Spring Initializr (start.spring.io) atau IDE seperti IntelliJ IDEA.

Menggunakan Spring Initializr

Setup
# 1. Kunjungi https://start.spring.io/
# 2. Pilih:
#    - Project: Maven
#    - Language: Java
#    - Spring Boot: 3.3.x (terbaru)
#    - Group: com.example
#    - Artifact: demo-api
#    - Packaging: Jar
#    - Java: 21
#    - Dependencies: Spring Web, Spring Data JPA, H2 Database,
#                     Spring Security, Lombok
# 3. Klik "Generate" → download zip → extract

# Struktur folder:
demo-api/
├── src/
│   ├── main/
│   │   ├── java/com/example/demoapi/
│   │   │   ├── DemoApiApplication.java    ← Main class
│   │   │   ├── controller/
│   │   │   ├── service/
│   │   │   ├── repository/
│   │   │   └── model/
│   │   └── resources/
│   │       ├── application.properties     ← Konfigurasi
│   │       ├── static/
│   │       └── templates/
│   └── test/
└── pom.xml                                ← Dependencies

pom.xml — Dependencies

XML — pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="...">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.0</version>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>demo-api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Demo API</name>

    <properties>
        <java.version>21</java.version>
    </properties>

    <dependencies>
        <!-- Spring Web — REST API -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Data JPA — Database ORM -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- H2 Database — In-memory untuk development -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- Lombok — Mengurangi boilerplate -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Validation -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Menjalankan Aplikasi

Terminal
# Jalankan dari terminal
./mvnw spring-boot:run

# Atau build dulu lalu run
./mvnw clean package -DskipTests
java -jar target/demo-api-0.0.1-SNAPSHOT.jar

# Aplikasi berjalan di http://localhost:8080
# Console output:
#   Tomcat started on port 8080 (http)
#   Started DemoApiApplication in 2.3 seconds

application.properties

Properties — application.properties
# Server
server.port=8080

# H2 Database (in-memory)
spring.datasource.url=jdbc:h2:mem:demo
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# JPA / Hibernate
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# Application name
spring.application.name=demo-api

3. Dependency Injection (DI)

Dependency Injection adalah prinsip inti dari Spring. Alih-alih membuat objek dependency secara manual, Spring akan menyuntikkan (inject) dependency secara otomatis. Ini membuat kode lebih modular, testable, dan loosely coupled.

Java — Dependency Injection
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// ===== Component — Generic bean =====
@Component
public class EmailService {
    public void kirimEmail(String ke, String pesan) {
        System.out.println("Email ke " + ke + ": " + pesan);
    }
}

// ===== Service — Business logic layer =====
@Service
public class UserService {

    // Constructor Injection (RECOMMENDED)
    private final EmailService emailService;
    private final UserRepository userRepository;

    // @Autowired bisa dihilangkan jika hanya 1 constructor
    public UserService(EmailService emailService,
                       UserRepository userRepository) {
        this.emailService = emailService;
        this.userRepository = userRepository;
    }

    public void daftarPengguna(String nama, String email) {
        User user = new User(nama, email);
        userRepository.save(user);
        emailService.kirimEmail(email, "Selamat datang, " + nama + "!");
    }
}

// Selain constructor injection, ada juga:

// Field Injection (tidak direkomendasikan, sulit di-test)
// @Autowired
// private EmailService emailService;

// Setter Injection
// private EmailService emailService;
// @Autowired
// public void setEmailService(EmailService service) {
//     this.emailService = service;
// }

Stereotype Annotations

Annotation Layer Gunanya
@ComponentUmumGeneric Spring-managed bean
@ServiceBusinessService layer (logika bisnis)
@RepositoryDataData access layer (database)
@ControllerWebWeb MVC controller
@RestControllerREST API@Controller + @ResponseBody (JSON)

Bean Scopes

Java — Bean Scopes
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

// Singleton (default) — satu instance untuk seluruh aplikasi
@Component
@Scope("singleton")
public class SingletonBean { }

// Prototype — instance baru setiap kali di-inject
@Component
@Scope("prototype")
public class PrototypeBean { }

// Request — instance baru per HTTP request (web only)
// @Component @Scope("request")

// Session — instance baru per user session (web only)
// @Component @Scope("session")
💡 Tips

Selalu gunakan constructor injection (bukan field injection). Constructor injection membuat dependency bisa di-test dengan mudah (bisa mock), field immutable (final), dan komponen tidak bisa dibuat tanpa dependency yang lengkap. Field injection terlihat lebih ringkas tapi membuat testing lebih sulit.

4. REST Controller

REST Controller menangani HTTP request dan mengembalikan response (biasanya dalam format JSON). Spring Boot membuat ini sangat mudah dengan anotasi seperti @GetMapping, @PostMapping, dll.

Java — Entity
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import lombok.*;

// Entity — merepresentasikan tabel di database
@Entity
@Table(name = "produk")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Produk {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "Nama tidak boleh kosong")
    @Size(min = 2, max = 100)
    private String nama;

    @NotNull(message = "Harga tidak boleh kosong")
    @Positive(message = "Harga harus positif")
    private Double harga;

    @NotNull
    @Min(0)
    private Integer stok;

    @NotBlank
    private String kategori;

    private boolean aktif = true;
}
Java — Repository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface ProdukRepository extends JpaRepository<Produk, Long> {

    // Spring Data otomatis buat query dari method name!
    List<Produk> findByKategori(String kategori);

    List<Produk> findByNamaContainingIgnoreCase(String keyword);

    List<Produk> findByHargaBetween(Double min, Double max);

    List<Produk> findByAktifTrue();

    // Custom JPQL query
    @Query("SELECT p FROM Produk p WHERE p.harga = " +
           "(SELECT MAX(p2.harga) FROM Produk p2)")
    Produk findTermahal();

    // Native SQL query
    @Query(value = "SELECT * FROM produk WHERE stok > 0",
           nativeQuery = true)
    List<Produk> findStokTersedia();
}
Java — Service
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class ProdukService {

    private final ProdukRepository repository;

    public ProdukService(ProdukRepository repository) {
        this.repository = repository;
    }

    public List<Produk> getAll() {
        return repository.findAll();
    }

    public Produk getById(Long id) {
        return repository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException(
                "Produk dengan ID " + id + " tidak ditemukan"));
    }

    public Produk create(Produk produk) {
        return repository.save(produk);
    }

    public Produk update(Long id, Produk update) {
        Produk existing = getById(id);
        existing.setNama(update.getNama());
        existing.setHarga(update.getHarga());
        existing.setStok(update.getStok());
        existing.setKategori(update.getKategori());
        return repository.save(existing);
    }

    public void delete(Long id) {
        Produk existing = getById(id);
        repository.delete(existing);
    }

    public List<Produk> getByKategori(String kategori) {
        return repository.findByKategori(kategori);
    }

    public List<Produk> search(String keyword) {
        return repository.findByNamaContainingIgnoreCase(keyword);
    }
}
Java — REST Controller
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import java.util.List;

@RestController
@RequestMapping("/api/produk")
public class ProdukController {

    private final ProdukService service;

    public ProdukController(ProdukService service) {
        this.service = service;
    }

    // GET /api/produk — Ambil semua produk
    @GetMapping
    public ResponseEntity<List<Produk>> getAll() {
        return ResponseEntity.ok(service.getAll());
    }

    // GET /api/produk/{id} — Ambil produk by ID
    @GetMapping("/{id}")
    public ResponseEntity<Produk> getById(@PathVariable Long id) {
        return ResponseEntity.ok(service.getById(id));
    }

    // POST /api/produk — Buat produk baru
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Produk create(@Valid @RequestBody Produk produk) {
        return service.create(produk);
    }

    // PUT /api/produk/{id} — Update produk
    @PutMapping("/{id}")
    public ResponseEntity<Produk> update(
            @PathVariable Long id,
            @Valid @RequestBody Produk produk) {
        return ResponseEntity.ok(service.update(id, produk));
    }

    // DELETE /api/produk/{id} — Hapus produk
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable Long id) {
        service.delete(id);
    }

    // GET /api/produk/kategori/{kategori} — Filter by kategori
    @GetMapping("/kategori/{kategori}")
    public ResponseEntity<List<Produk>> getByKategori(
            @PathVariable String kategori) {
        return ResponseEntity.ok(service.getByKategori(kategori));
    }

    // GET /api/produk/search?q=keyword — Cari produk
    @GetMapping("/search")
    public ResponseEntity<List<Produk>> search(
            @RequestParam String q) {
        return ResponseEntity.ok(service.search(q));
    }
}

HTTP Methods pada REST

Method URL Aksi Annotation
GET/api/produkAmbil semua@GetMapping
GET/api/produk/1Ambil by ID@GetMapping("/{id}")
POST/api/produkBuat baru@PostMapping
PUT/api/produk/1Update@PutMapping("/{id}")
DELETE/api/produk/1Hapus@DeleteMapping("/{id}")

5. JPA — Database Persistence

Spring Data JPA adalah modul yang menyederhanakan akses database. Dengan mendefinisikan interface JpaRepository, Spring otomatis membuat implementasi CRUD tanpa perlu menulis SQL manual.

One-to-Many Relationship

Java — Entity Relationships
import jakarta.persistence.*;
import lombok.*;
import java.util.List;

// ===== Parent Entity: Kategori =====
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Kategori {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    private String deskripsi;

    // One Kategori → Many Produk
    @OneToMany(mappedBy = "kategori", cascade = CascadeType.ALL,
               fetch = FetchType.LAZY)
    @ToString.Exclude  // Hindari infinite loop
    private List<Produk> daftarProduk;
}

// ===== Child Entity: Produk =====
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Produk {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String nama;

    private Double harga;
    private Integer stok;

    // Many Produk → One Kategori
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "kategori_id")
    @ToString.Exclude
    private Kategori kategori;
}

JpaRepository Methods yang Tersedia Otomatis

Method Return Gunanya
findAll()List<T>Ambil semua data
findById(id)Optional<T>Ambil by ID
save(entity)TInsert atau Update
saveAll(list)List<T>Batch insert/update
deleteById(id)voidHapus by ID
delete(entity)voidHapus entity
count()longHitung total record
existsById(id)booleanCek keberadaan

6. Global Exception Handling

Spring Boot memungkinkan kita menangani error secara terpusat menggunakan @ControllerAdvice dan @ExceptionHandler. Ini menghindari duplikasi try-catch di setiap controller.

Java — Global Exception Handler
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

// Custom Exception
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

// Error Response DTO
public record ApiError(
    LocalDateTime timestamp,
    int status,
    String error,
    String message,
    Map<String, String> validationErrors
) {}

// Global Exception Handler
@RestControllerAdvice
public class GlobalExceptionHandler {

    // Handle Resource Not Found (404)
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ApiError> handleNotFound(
            ResourceNotFoundException ex) {
        ApiError error = new ApiError(
            LocalDateTime.now(),
            404,
            "Not Found",
            ex.getMessage(),
            null
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    // Handle Validation Errors (400)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiError> handleValidation(
            MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(e ->
            errors.put(e.getField(), e.getDefaultMessage())
        );

        ApiError error = new ApiError(
            LocalDateTime.now(),
            400,
            "Validation Failed",
            "Data tidak valid",
            errors
        );
        return ResponseEntity.badRequest().body(error);
    }

    // Handle generic errors (500)
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiError> handleGeneral(Exception ex) {
        ApiError error = new ApiError(
            LocalDateTime.now(),
            500,
            "Internal Server Error",
            "Terjadi kesalahan pada server",
            null
        );
        return ResponseEntity.internalServerError().body(error);
    }
}
💡 Tips

Pisahkan layer dengan jelas: Controller hanya menangani HTTP (request/response), Service berisi logika bisnis, Repository hanya akses database. Jangan letakkan query SQL di controller! Ini prinsip Separation of Concerns yang membuat kode lebih maintainable.

7. Spring Security Basics

Spring Security adalah framework untuk mengamankan aplikasi Spring. Secara default, semua endpoint dilindungi. Kita perlu mengkonfigurasi akses mana yang publik dan mana yang memerlukan autentikasi.

Java — Security Configuration
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.*;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    // Konfigurasi Security Filter Chain
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())  // Disable CSRF untuk REST API
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                // Endpoint publik — tidak perlu login
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/h2-console/**").permitAll()
                // GET untuk semua user
                .requestMatchers(HttpMethod.GET, "/api/produk/**").permitAll()
                // POST, PUT, DELETE hanya untuk ADMIN
                .requestMatchers(HttpMethod.POST, "/api/produk/**").hasRole("ADMIN")
                .requestMatchers(HttpMethod.PUT, "/api/produk/**").hasRole("ADMIN")
                .requestMatchers(HttpMethod.DELETE, "/api/produk/**").hasRole("ADMIN")
                // Semua request lain perlu autentikasi
                .anyRequest().authenticated()
            )
            .httpBasic();  // Basic Authentication

        return http.build();
    }

    // In-memory user (untuk development)
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails admin = User.builder()
            .username("admin")
            .password(passwordEncoder().encode("admin123"))
            .roles("ADMIN")
            .build();

        UserDetails user = User.builder()
            .username("user")
            .password(passwordEncoder().encode("user123"))
            .roles("USER")
            .build();

        return new InMemoryUserDetailsManager(admin, user);
    }

    // Password encoder — enkripsi password
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Testing API dengan Authentication

Terminal — API Testing
# GET semua produk (publik — tanpa auth)
curl http://localhost:8080/api/produk

# POST buat produk baru (perlu auth ADMIN)
curl -X POST http://localhost:8080/api/produk \
  -H "Content-Type: application/json" \
  -u admin:admin123 \
  -d '{"nama":"Laptop","harga":12000000,"stok":10,"kategori":"Elektronik"}'

# GET produk by ID
curl http://localhost:8080/api/produk/1

# PUT update produk
curl -X PUT http://localhost:8080/api/produk/1 \
  -H "Content-Type: application/json" \
  -u admin:admin123 \
  -d '{"nama":"Laptop Gaming","harga":15000000,"stok":5,"kategori":"Elektronik"}'

# DELETE produk (admin only)
curl -X DELETE http://localhost:8080/api/produk/1 -u admin:admin123

# Coba akses tanpa auth (akan 401 Unauthorized)
curl http://localhost:8080/api/produk -X POST \
  -H "Content-Type: application/json" \
  -d '{"nama":"Test"}'
# Response: 401 Unauthorized

JWT Authentication (Produksi)

Java — JWT Token Flow
// Flow JWT Authentication:
// 1. Client POST /api/auth/login {username, password}
// 2. Server validasi → generate JWT token
// 3. Client simpan token (localStorage / cookie)
// 4. Client kirim token di header: Authorization: Bearer <token>
// 5. Server validasi token di setiap request

// Dependencies untuk JWT (tambahkan di pom.xml):
// io.jsonwebtoken:jjwt-api:0.12.5
// io.jsonwebtoken:jjwt-impl:0.12.5
// io.jsonwebtoken:jjwt-jackson:0.12.5

// Contoh generate token (simplified):
// String token = Jwts.builder()
//     .subject(username)
//     .claim("roles", roles)
//     .issuedAt(new Date())
//     .expiration(new Date(System.currentTimeMillis() + 3600000))
//     .signWith(secretKey)
//     .compact();

// Di produksi, gunakan JWT bukan Basic Auth!

8. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Spring Boot:

Pertanyaan 1: Anotasi apa yang digunakan untuk membuat class menjadi REST Controller di Spring Boot?

a) @Controller
b) @RestController
c) @WebService
d) @ApiEndpoint

Pertanyaan 2: Jenis dependency injection apa yang direkomendasikan di Spring Boot?

a) Field Injection dengan @Autowired
b) Setter Injection
c) Constructor Injection
d) Method Injection

Pertanyaan 3: HTTP method apa yang digunakan untuk membuat data baru di REST API?

a) GET
b) PUT
c) POST
d) DELETE

Pertanyaan 4: Apa fungsi dari JpaRepository<Produk, Long>?

a) Membuat REST controller otomatis
b) Menyediakan operasi CRUD otomatis untuk entity Produk dengan ID bertipe Long
c) Mengkonfigurasi security
d) Membuat UI form

Pertanyaan 5: Anotasi apa yang digunakan untuk menangani exception secara global di semua controller?

a) @GlobalException
b) @ExceptionHandler di dalam @RestControllerAdvice
c) @ErrorMapper
d) @TryCatch
🔍 Zoom
100%
🎨 Tema