Senior back-end engineer (Java) assessment task

This task is part of our practical seniority assessment for Java backend roles. It is deliberately limited in scope and is intended to support a focused technical discussion.

Purpose

You are asked to build a small Java microservice for a payment domain.

The goal is not to produce the most elaborate solution. We are looking for a correct, working implementation with sensible technical choices and clear scope control. Keep it focused and avoid over-engineering.

Time reference

We use this task with internal timing benchmarks as part of our seniority assessment.

For this specific task, our benchmark is as follows:

  • a senior-level engineer should usually be able to reach a working baseline in about 3 hours;
  • a mid-level engineer will typically need up to 12 hours;
  • if it takes materially longer than 12 hours, we would usually treat that as junior-level execution for this scope.

These timings come from our own calibration of the task. They are a practical reference point, not a decision on their own. A working solution still comes first. We consider timing alongside correctness, implementation quality, scope control, and technical reasoning.

For broader context on how timing fits into the process, see How we assess seniority.

Use of tools

This task is intended to reflect your own practical level of execution within the stated scope and timing benchmark.

You may use standard documentation, reference materials, and search tools such as Google while working on it. AI tools are not allowed. This includes code generation tools, AI assistants, and any other system used to generate, rewrite, suggest, or complete code or technical content on your behalf.

The task must represent your own work.

Technical baseline

Use the following stack:

  • Java 17 or Java 21
  • Spring Boot
  • Maven
  • REST API only

Task overview

Implement a small microservice for managing payments through a REST API.

The service must support:

  • creating a payment;
  • retrieving a payment by ID;
  • updating a payment;
  • deleting a payment;
  • listing all payments;
  • changing payment status through dedicated status transition endpoints.

Payment model

The Payment entity must include at least these fields:

  • id
  • amount
  • currency
  • debtorAccount
  • creditorAccount
  • status
  • createdAt

The following rules apply:

  • id is generated by the backend;
  • status is managed by the backend;
  • createdAt is generated by the backend.

You may extend the model if you think it is useful, but any additions should remain proportionate to the scope of the task.

Functional requirements

Your service must support these operations:

  • create a new payment;
  • get a payment by ID;
  • update an existing payment;
  • delete a payment;
  • list all payments;
  • mark a payment as completed;
  • mark a payment as failed.

API endpoints

Implement the following endpoints.

POST /payments

Creates a new payment.

Example request:

{
  "amount": 100.0,
  "currency": "EUR",
  "debtorAccount": "DE123456789",
  "creditorAccount": "DE987654321"
}

Example successful response:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "amount": 100.0,
  "currency": "EUR",
  "debtorAccount": "DE123456789",
  "creditorAccount": "DE987654321",
  "status": "CREATED",
  "createdAt": "2026-04-14T10:15:30Z"
}

GET /payments/{id}

Returns a payment by ID.

PUT /payments/{id}

Updates an existing payment.

Example request:

{
  "amount": 150.0,
  "currency": "EUR",
  "debtorAccount": "DE123456789",
  "creditorAccount": "DE987654321"
}

DELETE /payments/{id}

Deletes an existing payment, subject to the business rules below.

GET /payments

Returns all payments.

POST /payments/{id}/complete

Sets the payment status to COMPLETED.

POST /payments/{id}/fail

Sets the payment status to FAILED.

Business rules

Apply the following rules.

Creation

  • amount must be greater than 0;
  • currency is required;
  • debtorAccount is required;
  • creditorAccount is required;
  • the client must not set id;
  • the client must not set status;
  • the client must not set createdAt;
  • a newly created payment must have status CREATED.

Updates

  • only mutable business fields may be updated;
  • status must not be updated through PUT /payments/{id};
  • id and createdAt must remain unchanged;
  • payments with status COMPLETED must not be modified;
  • payments with status FAILED must not be modified.

Status transitions

  • only payments in status CREATED may be moved to COMPLETED;
  • only payments in status CREATED may be moved to FAILED;
  • payments in status COMPLETED are immutable;
  • payments in status FAILED are immutable.

Deletion

  • payments in status COMPLETED must not be deleted;
  • payments in status FAILED must not be deleted;
  • payments in status CREATED may be deleted.

Error handling

Use standard HTTP status codes.

Error responses should be consistent and easy to understand. Validation errors and business rule violations should be handled clearly rather than being returned as generic internal server errors. The exact error response structure is up to you, but it should be coherent and usable.

Persistence and runtime

You may use either of the following:

  • H2 embedded database with no additional setup required; or
  • a database running in a container.

If you use a containerised database, the full solution must be startable with a single command. The application must run locally.

Build and run requirements

The project must include:

  • Maven build configuration;
  • a Dockerfile;
  • a README explaining:
    • how to build the project;
    • how to run the service locally;
    • how to test the API.

If you use a containerised database, the README must include the single command needed to start the full setup. You may also include sample requests or short design notes if they help.

Out of scope

The following are not required for this exercise:

  • frontend implementation;
  • authentication or authorisation;
  • cloud deployment;
  • pagination;
  • message brokers;
  • distributed workflow orchestration;
  • advanced observability setup;
  • automated tests.

Keep the solution proportionate to the task.

What we pay attention to

The first requirement is that the solution works.

Beyond that, we look at whether the implementation is correct, pragmatic, clearly structured, and appropriately scoped. We also look at how clearly you can explain your decisions and trade-offs during the technical discussion.

Assessment materials and confidentiality

This task and the related assessment materials are provided solely for use in our hiring process. The task brief, its structure, examples, and associated assessment methodology are proprietary to us and must not be copied, shared, republished, distributed, or made public in any form, whether in full or in part. This includes posting the task, extracts from it, or materials that reproduce its substance on websites, social platforms, public code repositories, forums, blogs, or training libraries.

Any solution prepared in response to this task must be submitted for assessment purposes only and must not be published together with the task content or in a way that discloses the task itself. If you would like to refer to your work privately as part of a future application process, please do so only in a way that does not disclose or reproduce our assessment materials.

Related page

For background on how this task fits into our hiring process, including how timing is used as one benchmark, see How we assess seniority.