0Pricing
Spring Boot 4 Complete Guide · Pelajaran

Langganan, Kesalahan, dan Keamanan Skema

Alirkan pembaruan waktu nyata melalui langganan dan perkuat skema dengan otorisasi tingkat bidang.

Langganan, Kesalahan, dan Keamanan Skema adalah pelajaran Spring Boot 4 Complete Guide gratis di CoddyKit. Ini adalah pelajaran 4 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar Spring Boot 4 Complete Guide, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Spring Boot 4 Complete Guide mencakup 4 pelajaran total.

Bagian dari pelajaran ini belum diterjemahkan dan ditampilkan dalam bahasa Inggris.

Beyond Query and Mutation

Spring for GraphQL supports three root operation types. So far you have used Query (read) and Mutation (write). The third is Subscription — a long-lived stream that pushes data to the client whenever a server-side event occurs.

  • Query/Mutation: one request, one response.
  • Subscription: one request, many responses over time.

In this lesson you will stream live updates with subscriptions, shape GraphQL errors cleanly, and lock down individual fields with method security.

Declaring a Subscription in the Schema

Subscriptions are declared in the GraphQL schema just like queries. Each subscription field describes a stream of a given type. Below, messageAdded streams a Message for a particular room.

The transport for subscriptions is typically WebSocket (graphql-ws protocol), so make sure your client connects over ws:// rather than plain HTTP POST.

type Subscription {
  messageAdded(roomId: ID!): Message!
}

type Message {
  id: ID!
  roomId: ID!
  text: String!
  author: String!
}

A Subscription Returns a Reactive Stream

In Spring for GraphQL, a method annotated with @SubscriptionMapping must return a Reactive Streams Publisher — most commonly a Project Reactor Flux. Each element the Flux emits is delivered to the subscribed client as a separate GraphQL response.

  • The method name maps to the subscription field (messageAdded).
  • Arguments come from @Argument, exactly like queries.
  • The Flux stays open until it completes, errors, or the client disconnects.
@Controller
public class ChatSubscriptionController {

    private final ChatService chatService;

    public ChatSubscriptionController(ChatService chatService) {
        this.chatService = chatService;
    }

    @SubscriptionMapping
    public Flux<Message> messageAdded(@Argument String roomId) {
        return chatService.streamMessages(roomId);
    }
}

Backing the Stream with a Sink

Where does the Flux come from? A common pattern is a Reactor Sinks.Many as a hot multicast source. Mutations push new events into the sink with tryEmitNext, and every active subscriber receives them.

Use multicast().onBackpressureBuffer() so multiple subscribers can share one source, and filter per room so each client only gets its own messages.

@Service
public class ChatService {

    private final Sinks.Many<Message> sink =
            Sinks.many().multicast().onBackpressureBuffer();

    public Message publish(Message message) {
        sink.tryEmitNext(message);
        return message;
    }

    public Flux<Message> streamMessages(String roomId) {
        return sink.asFlux()
                .filter(m -> m.roomId().equals(roomId));
    }
}

Wiring a Mutation to the Stream

A subscription only emits when something feeds it. Here a @MutationMapping creates a message and publishes it into the sink. Any client subscribed to that room receives the new message immediately.

This decoupling — mutation writes, subscription reads from the same sink — is the backbone of real-time GraphQL.

@Controller
public class ChatMutationController {

    private final ChatService chatService;

    public ChatMutationController(ChatService chatService) {
        this.chatService = chatService;
    }

    @MutationMapping
    public Message postMessage(@Argument String roomId,
                               @Argument String text,
                               @Argument String author) {
        Message message = new Message(
                UUID.randomUUID().toString(), roomId, text, author);
        return chatService.publish(message);
    }
}

Enabling the WebSocket Transport

Subscriptions need the WebSocket endpoint enabled. With Spring Boot 4 and the GraphQL starter, set the path in application.yml. The HTTP endpoint (/graphql) stays for queries and mutations; the WebSocket endpoint (/graphql over ws) handles subscriptions.

Without this property the WebSocket handler is not registered and subscription clients fail to connect.

spring:
  graphql:
    websocket:
      path: /graphql
      connection-init-timeout: 60s
    schema:
      printer:
        enabled: true

Why Default GraphQL Errors Leak Detail

When an exception escapes a controller method, Spring for GraphQL turns it into a GraphQL error. By default many exceptions surface as INTERNAL_ERROR with a generic message, but stack traces and unexpected exception messages can leak implementation details if you are not careful.

The fix is a DataFetcherExceptionResolver: map your domain exceptions to clean, well-classified GraphQLError objects with the right ErrorType.

Mapping Exceptions with @GraphQlExceptionHandler

The simplest approach is an @GraphQlExceptionHandler method inside a @Controller (or a @ControllerAdvice for global scope). It works like Spring MVC exception handling: catch a specific exception and return a GraphQLError.

  • ErrorType.NOT_FOUND → resource missing.
  • ErrorType.BAD_REQUEST → invalid input.
  • ErrorType.FORBIDDEN → authorization failure.

The extensions map carries machine-readable detail the client can act on.

@ControllerAdvice
public class GraphQlExceptionAdvice {

    @GraphQlExceptionHandler
    public GraphQLError handleNotFound(MessageNotFoundException ex) {
        return GraphQLError.newError()
                .errorType(ErrorType.NOT_FOUND)
                .message(ex.getMessage())
                .extensions(Map.of("code", "MESSAGE_NOT_FOUND"))
                .build();
    }
}

Field-Level Authorization with @PreAuthorize

GraphQL exposes a single endpoint, so you cannot rely on URL-based security. Instead secure individual fields at the method level. With Spring Security's method security enabled (@EnableMethodSecurity), annotate controller methods with @PreAuthorize.

If the check fails, the field resolves to null and an authorization error is added to the GraphQL errors array — sibling fields still resolve normally.

@Controller
public class AdminController {

    @QueryMapping
    @PreAuthorize("hasRole('ADMIN')")
    public List<AuditEntry> auditLog() {
        return auditService.findAll();
    }

    @SchemaMapping(typeName = "Message", field = "author")
    @PreAuthorize("isAuthenticated()")
    public String author(Message message) {
        return message.author();
    }
}

Propagating Security to Reactive Subscriptions

Subscriptions run on the reactive WebSocket transport, so the SecurityContext must travel through the reactive chain. Spring Security populates the Reactor context; access the authenticated principal with ReactiveSecurityContextHolder rather than the thread-local SecurityContextHolder.

This lets you filter a subscription stream by the current user — for example, only emitting messages from rooms the user belongs to.

@SubscriptionMapping
@PreAuthorize("isAuthenticated()")
public Flux<Message> messageAdded(@Argument String roomId) {
    return ReactiveSecurityContextHolder.getContext()
            .map(ctx -> ctx.getAuthentication().getName())
            .flatMapMany(user ->
                    chatService.streamMessages(roomId, user));
}

A Standalone Flux Stream Demo

You do not need a running server to understand how a subscription stream behaves. The example below uses a plain Reactor Flux with a filter — exactly the shape streamMessages returns — and prints each emitted element, mimicking what a subscribed client would receive.

Notice how only messages matching the room pass through, just like server-side per-room filtering.

import reactor.core.publisher.Flux;

public class StreamDemo {
    record Message(String roomId, String text) {}

    public static void main(String[] args) {
        Flux<Message> source = Flux.just(
                new Message("general", "hi"),
                new Message("random", "noise"),
                new Message("general", "streaming works"));

        source.filter(m -> m.roomId().equals("general"))
              .subscribe(m -> System.out.println("Push -> " + m.text()));
    }
}

Quick Check: Securing a Field

You want only users with the ADMIN role to be able to read the auditLog query field, while every other field in the same response keeps resolving normally for all users. Which approach fits GraphQL best?

Recap

You now have the real-time and security toolkit for Spring for GraphQL:

  • Subscriptions are declared in the schema and implemented with @SubscriptionMapping returning a reactive Flux/Publisher.
  • A Sinks.Many hot source lets mutations push events that subscribers stream, filtered per room.
  • Enable the WebSocket transport via spring.graphql.websocket.path.
  • Map domain exceptions to clean GraphQLErrors with @GraphQlExceptionHandler and the right ErrorType.
  • Secure individual fields with @PreAuthorize; failures null the field and add an error without breaking siblings.
  • For subscriptions, read the principal from the reactive ReactiveSecurityContextHolder.

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Langganan, Kesalahan, dan Keamanan Skema” gratis?

Ya — teks lengkap “Langganan, Kesalahan, dan Keamanan Skema” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Spring Boot 4 Complete Guide, upgrade ke CoddyKit PRO. Kursus Spring Boot 4 Complete Guide mencakup 4 pelajaran total.

Apa yang akan aku pelajari di “Langganan, Kesalahan, dan Keamanan Skema”?

Alirkan pembaruan waktu nyata melalui langganan dan perkuat skema dengan otorisasi tingkat bidang. Kamu berlatih Spring Boot 4 Complete Guide dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.

Apakah aku perlu pengalaman untuk memulai Spring Boot 4 Complete Guide?

Tidak diperlukan pengalaman sebelumnya. Spring Boot 4 Complete Guide di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 4 dari 4.

Berapa lama pelajaran “Langganan, Kesalahan, dan Keamanan Skema” memakan waktu?

Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.

Bisakah aku menulis dan menjalankan kode dalam pelajaran Spring Boot 4 Complete Guide ini?

Ya. Setiap pelajaran Spring Boot 4 Complete Guide menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.

Semua pelajaran dalam kursus ini

  1. Desain Berbasis Skema dan Pemetaan Tipe
  2. Pengambil Data dan Pengikatan Argumen
  3. Mengatasi N+1 dengan Pemuat Batch
  4. Langganan, Kesalahan, dan Keamanan Skema
← Kembali ke Spring Boot 4 Complete Guide