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:
idamountcurrencydebtorAccountcreditorAccountstatuscreatedAt
The following rules apply:
idis generated by the backend;statusis managed by the backend;createdAtis 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
amountmust be greater than 0;currencyis required;debtorAccountis required;creditorAccountis 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;
statusmust not be updated throughPUT /payments/{id};idandcreatedAtmust remain unchanged;- payments with status
COMPLETEDmust not be modified; - payments with status
FAILEDmust not be modified.
Status transitions
- only payments in status
CREATEDmay be moved toCOMPLETED; - only payments in status
CREATEDmay be moved toFAILED; - payments in status
COMPLETEDare immutable; - payments in status
FAILEDare immutable.
Deletion
- payments in status
COMPLETEDmust not be deleted; - payments in status
FAILEDmust not be deleted; - payments in status
CREATEDmay 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
READMEexplaining:- 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.