Spring Boot Server

GraphLink generates the entire Spring Boot scaffolding from your schema — controllers, service interfaces, types, inputs, and enums.

Server mode config

Set "mode": "server" and provide a "spring" section under serverConfig. The key options:

spring-config.json JSON
{
  "schemaPaths": ["schema/*.graphql"],
  "mode": "server",
  "typeMappings": {
    "ID":      "String",
    "String":  "String",
    "Float":   "Double",
    "Int":     "Integer",
    "Boolean": "Boolean",
    "Null":    "null"
  },
  "outputDir": "src/main/java/com/example/generated",
  "serverConfig": {
    "spring": {
      "basePackage":          "com.example.generated",
      "generateControllers":  true,
      "generateInputs":       true,
      "generateTypes":        true,
      "generateRepositories": false,
      "immutableInputFields": true,
      "immutableTypeFields":  false
    }
  }
}
OptionDescription
generateControllersGenerates @Controller classes with @QueryMapping, @MutationMapping, @SubscriptionMapping, and @Argument on parameters.
generateInputsGenerates input classes from input type definitions.
generateTypesGenerates entity/response classes from type definitions.
generateRepositoriesWhen true, generates JPA Repository interfaces for types annotated with @glRepository.
immutableInputFieldsInput class fields are final. Recommended: true.
immutableTypeFieldsType class fields are final. Set to false for Spring Boot — Spring's GraphQL runtime sets fields via setters.

What gets generated

For the example schema, the generator produces 9 files. Here is the complete output tree:

src/main/java/com/example/generated/
controllers/
PersonServiceController.java VehicleServiceController.java
services/
PersonService.java VehicleService.java
types/
Person.java Vehicle.java
inputs/
AddPersonInput.java AddVehicleInput.java
enums/
FuelType.java

Highlighted files are the ones you interact with. Controllers are generated and never touched by hand. Service interfaces are what you implement. Types, inputs, and enums are data classes.

Types and inputs

Server-side types are mutable — they have getters and setters, not final fields. This is required because Spring's GraphQL runtime deserializes JSON into these classes using reflection.

generated/types/Vehicle.java — server version (mutable) Java
public class Vehicle {
   private String id;
   private String brand;
   private String model;
   private Integer year;
   private FuelType fuelType;
   private String ownerId;

   public Vehicle() {}

   public String getId() { return id; }
   public void setId(String id) { this.id = id; }

   public String getBrand() { return brand; }
   public void setBrand(String brand) { this.brand = brand; }

   public String getModel() { return model; }
   public void setModel(String model) { this.model = model; }

   public Integer getYear() { return year; }
   public void setYear(Integer year) { this.year = year; }

   public FuelType getFuelType() { return fuelType; }
   public void setFuelType(FuelType fuelType) { this.fuelType = fuelType; }

   public String getOwnerId() { return ownerId; }
   public void setOwnerId(String ownerId) { this.ownerId = ownerId; }
}

Input classes on the server use the same structure as the client — they can be immutable since Spring maps query arguments into them at the framework level using constructors or builders. Note that immutableTypeFields: false applies to type definitions only; input classes follow immutableInputFields.

Service interfaces

For each group of operations sharing a root type, GraphLink generates one service interface. The VehicleService interface covers every operation that involves a Vehicle return type:

generated/services/VehicleService.java Java
public interface VehicleService {
   Vehicle getVehicle(String id);
   List<Vehicle> listVehicles();
   Vehicle addVehicle(AddVehicleInput input);
   Flux<Vehicle> vehicleAdded();
}

Observe the return types:

You implement this interface and annotate your implementation with @Service. You do not touch the generated controller.

Controllers

The generated controller is the glue between Spring's GraphQL runtime and your service. It is fully annotated and delegates every call to the service interface. You never need to modify it:

generated/controllers/VehicleServiceController.java Java
@Controller()
public class VehicleServiceController {
   private final VehicleService vehicleService;

   public VehicleServiceController(VehicleService vehicleService) {
      this.vehicleService = vehicleService;
   }

   @QueryMapping()
   public Vehicle getVehicle(@Argument() String id) {
      return vehicleService.getVehicle(id);
   }

   @QueryMapping()
   public List<Vehicle> listVehicles() {
      return vehicleService.listVehicles();
   }

   @MutationMapping()
   public Vehicle addVehicle(@Argument() AddVehicleInput input) {
      return vehicleService.addVehicle(input);
   }

   @SubscriptionMapping()
   public Flux<Vehicle> vehicleAdded() {
      return vehicleService.vehicleAdded();
   }
}

Spring's @QueryMapping, @MutationMapping, and @SubscriptionMapping use the method name to map to the schema field by convention. @Argument on method parameters maps GraphQL arguments to Java parameters by name.

Implementing the service

Create a @Service class in your own package (not in the generated package) that implements the generated interface:

com/example/service/VehicleServiceImpl.java — your code Java
package com.example.service;

import com.example.generated.services.VehicleService;
import com.example.generated.types.Vehicle;
import com.example.generated.inputs.AddVehicleInput;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Sinks;
import java.util.List;

@Service
public class VehicleServiceImpl implements VehicleService {

    private final VehicleRepository vehicleRepository;
    // A sink for pushing subscription events
    private final Sinks.Many<Vehicle> vehicleSink =
        Sinks.many().multicast().onBackpressureBuffer();

    public VehicleServiceImpl(VehicleRepository vehicleRepository) {
        this.vehicleRepository = vehicleRepository;
    }

    @Override
    public Vehicle getVehicle(String id) {
        return vehicleRepository.findById(id).orElse(null);
    }

    @Override
    public List<Vehicle> listVehicles() {
        return vehicleRepository.findAll();
    }

    @Override
    public Vehicle addVehicle(AddVehicleInput input) {
        Vehicle v = new Vehicle();
        v.setBrand(input.getBrand());
        v.setModel(input.getModel());
        v.setYear(input.getYear());
        v.setFuelType(input.getFuelType());
        v.setOwnerId(input.getOwnerId());
        Vehicle saved = vehicleRepository.save(v);
        // Push to all active subscriptions
        vehicleSink.tryEmitNext(saved);
        return saved;
    }

    @Override
    public Flux<Vehicle> vehicleAdded() {
        return vehicleSink.asFlux();
    }
}

Keep generated code separate
Put your implementations in a separate package from the generated code (e.g. com.example.service vs com.example.generated). This way, re-running the generator never overwrites your business logic.

Subscriptions with Reactor

Spring Boot GraphQL uses Project Reactor for subscriptions. The service interface returns Flux<T> — a reactive stream that emits items over time.

The recommended approach is Sinks.Many: a thread-safe construct that lets you push items from anywhere in your application (e.g. from a mutation handler, a message queue consumer, or a scheduled job):

Push-based subscription with Sinks Java
// Declare a multicast sink — supports multiple concurrent subscribers
private final Sinks.Many<Vehicle> vehicleSink =
    Sinks.many().multicast().onBackpressureBuffer();

// In vehicleAdded() — return the flux backed by the sink
@Override
public Flux<Vehicle> vehicleAdded() {
    return vehicleSink.asFlux();
}

// When a new vehicle is saved, push it to all subscribers
vehicleSink.tryEmitNext(savedVehicle);

// When the application shuts down (optional)
vehicleSink.tryEmitComplete();

Sinks.many().multicast() allows multiple GraphQL subscribers to receive the same events simultaneously. Each client that subscribes to vehicleAdded gets its own subscription to the flux.

Validation with @glValidate

Add @glValidate to a mutation in your schema to instruct GraphLink to generate a validateX() method in the service interface. The controller calls this method before the main method, giving you a place to throw validation exceptions before any business logic runs.

Schema with @glValidate GraphQL
type Mutation {
  addVehicle(input: AddVehicleInput!): Vehicle! @glValidate
}

With @glValidate on addVehicle, the generated service interface gains an extra method:

Generated VehicleService.java — with @glValidate Java
public interface VehicleService {
   // Called first by the controller — throw here to abort the mutation
   void validateAddVehicle(AddVehicleInput input);

   Vehicle addVehicle(AddVehicleInput input);
   List<Vehicle> listVehicles();
   Vehicle getVehicle(String id);
   Flux<Vehicle> vehicleAdded();
}

The generated controller calls validateAddVehicle before addVehicle. In your implementation, throw any exception to abort:

Implementing the validation method Java
@Override
public void validateAddVehicle(AddVehicleInput input) {
    if (input.getBrand() == null || input.getBrand().isBlank()) {
        throw new IllegalArgumentException("Brand must not be blank");
    }
    if (input.getYear() < 1886 || input.getYear() > 2100) {
        throw new IllegalArgumentException("Year out of valid range");
    }
    // Add any additional business-rule validation here
}

@Override
public Vehicle addVehicle(AddVehicleInput input) {
    // Only reached if validateAddVehicle did not throw
    // ...
}