Creating a GraphQL API with Spring Boot and GraphQL Java

GraphQL gives web and mobile clients one typed endpoint for fetching exactly the data they need. It suits products that combine catalogues, accounts and payments without creating a separate REST route for every screen.

Spring Boot and GraphQL Java provide a practical path: define a schema, map operations to Java methods, then add authentication, persistence and monitoring. Frontend teams can evolve the API alongside the application.

Designing the schema around client needs

Begin with client workflows rather than database tables. A catalogue may need searchable products, stock status and an add-product mutation. The schema makes each contract explicit and validates responses at runtime.

type Query {
  products(search: String): [Product!]!
}

type Product {
  id: ID!
  name: String!
  priceMinor: Int!
  currency: String!
}

type Mutation {
  createProduct(input: CreateProductInput!): Product!
}

input CreateProductInput {
  name: String!
  priceMinor: Int!
  currency: String!
}

Use non-null fields when the server guarantees them, and inputs for mutation arguments. For Australian commerce data, return the currency explicitly and store prices in minor units. Whether an AUD amount includes GST belongs in product metadata rather than an implicit resolver convention.

Setting up Spring Boot and GraphQL Java

Create a Spring Boot project with the web and Spring for GraphQL starters. Spring for GraphQL uses GraphQL Java as its execution engine.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-graphql</artifactId>
</dependency>

Place schema files as .graphqls files in src/main/resources/graphql. Spring Boot exposes the endpoint at /graphql; setting spring.graphql.graphiql.enabled=true enables the browser explorer for local development. Keep that setting out of production, where the endpoint should accept only approved operations.

Connecting queries, mutations and nested data

Keep controllers thin: query mappings call services, while services own business rules and transactions. That separation makes authorisation and persistence easier to test.

@QueryMapping
public List<Product> products(@Argument String search) {
    return productService.search(search);
}

Use distinct mappings for each operation and let injected services validate requests. Mutations should check permissions, enforce idempotency where needed and return typed errors rather than exposing database exceptions.

Useful mapping choices include:

Preventing N+1 queries and oversized responses

A nested resolver can trigger one database call per parent, creating an N+1 query pattern that slows catalogues. Spring’s @BatchMapping groups related records, while DataLoader coordinates requests within one GraphQL operation.

Apply the same discipline to collections: use cursor pagination, cap page sizes and reject excessive nesting. Cache stable reference data, but avoid personalised account fields. Measure query counts in integration tests so new relations do not multiply traffic.

For a multi-tenant SQL catalogue, Azure SQL elastic pools can consolidate many small tenant databases on shared capacity. Choose this pattern only after measuring connection density, isolation requirements and noisy-neighbour behaviour.

Securing and validating GraphQL operations

Spring Security can authenticate OIDC or JWT credentials before GraphQL executes. Authorisation still belongs in each resolver because a valid user may lack access to a specific account or mutation. With cookie authentication, retain CSRF protection and reject mutations sent over GET.

Treat query text as untrusted input. Use allowlists or persisted operations in production, limit depth and complexity, cap result sizes and restrict unnecessary introspection. Map expected failures to stable codes; keep stack traces, SQL and credentials out of client responses.

Controls worth testing include:

Testing the contract and operating in production

Use @GraphQlTest for focused resolver tests and HttpGraphQlTester to exercise the actual endpoint and generated schema. Test successful queries, invalid arguments, authorisation failures and nullability rules. Testcontainers can run the real database during CI, catching SQL and transaction differences that mocks miss.

Package the service as an executable jar and expose health and metrics through Spring Boot Actuator. Instrument resolver latency, database time, error rates and operation names. If Australian clients require data to remain in the country, evaluate Azure Australia East or AWS Sydney and document the selected region. Organisations subject to the Privacy Act 1988 still need a clear privacy notice, access controls and retention policy.

Extending the API for Australian delivery teams

Sydney and Melbourne are important centres for fintech and online retail, so a GraphQL API often sits between customer portals, mobile apps and partner systems. A fixed-scope release with clear extension points is a practical commercial fit for many Australian SMEs. Delivery and booking resolvers should also handle Australia’s time zones and state public holidays: store instants in UTC, then apply the correct IANA zone, since daylight-saving rules differ across states.

Before launch, an Australian team may call the final technical review a “squiz”. Useful additions can include subscription events, Stripe webhooks, PDF invoices and audit history. Payment creation should use AUD minor units carefully, and webhook processing must be idempotent; business invoices need ABNs where required. JavaWhizz’s web development services can pair the API with a customer portal, mobile client or WordPress front end while preserving the same Java domain model.