Nowadays, there are quite a lot AI coding assistants. In this blog, you will take a closer look at Qwen Code, a terminal based AI coding assistant. Qwen Code is optimized for Qwen3-Coder, so when you are using this AI model, it is definitely worth looking at. Enjoy!
1. Introduction
There are many AI models and also many AI coding assistants. Which one to choose is a hard question. It also depends whether you run the models locally or in the cloud. When running locally, Qwen3-Coder is a very good AI model to be used for programming tasks. In previous posts, DevoxxGenie, a JetBrains IDE plugin, often was used as an AI coding assistant. DevoxxGenie is nicely integrated within the JetBrains IDE’s. But, it is also a good thing to take a look at other AI coding assistants. And when you are using Qwen3-Coder, Qwen Code is an obvious choice.
Qwen Code is based on Google’s Gemini CLI and it is a terminal application. This is different from DevoxxGenie. However, you are able to access the terminal from within the JetBrains IDE’s, so you do not need to leave the IDE at all.
In this blog, you will take a closer look at Qwen Code, how to configure it, and how to use it.
Sources used in this blog can be found at GitHub.
2. Prerequisites
Prerequisites for reading this blog are:
- Some experience with AI coding assistants;
- If you want to compare to DevoxxGenie, take a look at a previous post.
3. Installation
In order to install Qwen Code, node.js 20+ is required.
Install node.js following the official installation instructions.
Execute the following commands.
# Download and install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"
# Download and install Node.js:
nvm install 24
# Verify the Node.js version:
node -v # Should print "v24.12.0".
# Verify npm version:
npm -v # Should print "11.6.2".
After successfully installing node.js, you install Qwen Code by means of the following command.
npm install -g @qwen-code/qwen-code@latest
4. Setup
Qwen Code is installed now, but first some configuration needs to be done. A complete list of all configurable settings can be found on GitHub.
4.1 Disable Usage Statistics
Many tools like to receive usage statistics by default. As so does Qwen Code. If you do not want this, you can disable this in the settings. Settings can be added on different levels, to make things easy, a user settings file will be used in this blog.
Navigate to your home directory and you will see a .qwen directory. Create a file settings.json in this .qwen directory. Disable the usage statistics.
{
"privacy": {
"usageStatisticsEnabled": false
}
}
4.2 Configure Model
In this blog, a local model setup is used, using Ollama as inference engine and Qwen3-Coder running as a local model. In order to create the setup for this, create a .env file in the .qwen directory.
- OPENAI_API_KEY: when using a local model, the contents does not matter.
- OPENAI_BASE_URL: the URL where the model can be accessed. Do note that you need to add /v1 at the end.
- OPENAI_MODEL: the model you want to use by default. Of course, this model needs to be available within Ollama.
The contents of the .env file is something as follows.
OPENAI_API_KEY="your-api-key"
OPENAI_BASE_URL="https://localhost:11434/v1"
OPENAI_MODEL="qwen3-coder:30b"
4.3 System Prompt
It is a good practice to add a system prompt to your AI coding assistant. You can add some instructions for the model in this system prompt. You can add it by creating a QWEN.md file in the .qwen directory. This will ensure a default system prompt for all your projects. If you want to use a more specific system prompt for a particular repository, you can add a QWEN.md file in the repository itself.
If you are developing in Java, Spring Boot, etc. the following system prompt can be used as an example.
You are an expert code assistant for a professional Java developer. All code examples, reviews, and explanations must be idiomatic to the following tech stack:
* Backend: Java (latest LTS), Spring Boot (latest stable), PostgreSQL.
* Frontend: Vue.js (latest stable), Angular (latest stable).
* Follow modern best practices for RESTful APIs, object-relational mapping, unit testing (JUnit), and frontend-backend integration.
* Prefer Maven for Java dependency management.
* Whenever database code is required, use PostgreSQL syntax and conventions.
* For frontend, use Vue composition API where applicable.
* Always explain your reasoning, and reference documentation when giving architectural advice.
* When unsure, ask clarifying questions before producing code.
4.4 Finetune Model Settings
It is also possible to finetune model settings. For example, for codings tasks, you want the model to be more deterministic in order that it will respond more factual. This can be realized by setting the temperature to 0.
Add the following to the settings.json file.
"model": {
"generationConfig": {
"samplingParams": {
"temperature": 0
}
}
}
5. First Startup
If you haven’t done it already, now is the time to clone the GitHub repository. Be sure to checkout the qwen-code branch. If you want to execute the commands from this blog, you first delete the QWEN.md file and the src/test directory.
Qwen Code is a terminal application, so you have some different options here:
- Open a terminal and navigate to the repository.
- Open your IDE, e.g. IntelliJ, and open a terminal from within IntelliJ (ALT+F12).
Start Qwen Code by typing qwen in the terminal.

The first time, Qwen Code will ask you how you want to authenticate for this project. In this blog, a local model is used, so choose for OpenAI. The previously configured environment variables are shown, just confirm these settings.
A first simple command is to show the memory content (/memory show). It should output the system prompt you configured earlier. As you can see, Qwen Code shows exactly which content and where it is retrieved from.

Now, in order to verify whether the connection with the model is functioning correctly, just enter a simple prompt like how are you?
At the moment of writing Qwen Code 0.6.0 was used. Using qwen3-coder and even qwen3 as local model resulted in errors because these models are no reasoning models. An issue was created for this, and this was fixed very quickly. From version 0.6.1, the errors disappeared.
6. Create a Test
Let’s continue with something useful and create a test for the CustomersController.
Using the @ character, you can add files to the context. When typing, a search is executed and using the arrows, you can easily select the file you need. Using tab, you select the file. After that, you can complete the prompt. The prompt used is:
@src/main/java/com/mydeveloperplanet/myaicodeprojectplanet/controller/CustomersController.java
Write a unit test for this code using JUnit.
Use WebMvcTest.
Use MockMvc.
Use AssertJ assertions.
Add the test in this repository
Qwen Code starts analyzing the file and writes the test.


The test created can be seen below. During generating the test, Qwen Code tries to invoke mvn test in order to verify whether the test succeeds. The test fails, and the model starts changing the test, but each time it makes things worse. It seems that the model is in some kind of loop. When you take a look at the first created test, then the solution is quite obvious: a wrong import statement is present and if you comment this out, the problem is solved.
package com.mydeveloperplanet.myaicodeprojectplanet.controller;
import com.mydeveloperplanet.myaicodeprojectplanet.model.Customer;
//import com.mydeveloperplanet.myaicodeprojectplanet.openapi.model.Customer as OpenApiCustomer;
import com.mydeveloperplanet.myaicodeprojectplanet.service.CustomerService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(CustomersController.class)
class CustomersControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private CustomerService customerService;
@Test
@DisplayName("GET /customers returns list of customers")
void getAllCustomersReturnsList() throws Exception {
Customer customer = new Customer(1L, "John", "Doe");
when(customerService.getAllCustomers()).thenReturn(List.of(customer));
MvcResult result = mockMvc.perform(get("/customers"))
.andExpect(status().isOk())
.andReturn();
String content = result.getResponse().getContentAsString();
assertThat(content).contains("John");
}
@Test
@DisplayName("GET /customers/{id} returns customer when found")
void getCustomerByIdFound() throws Exception {
Customer customer = new Customer(1L, "Jane", "Smith");
when(customerService.getCustomerById(1L)).thenReturn(Optional.of(customer));
mockMvc.perform(get("/customers/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.firstName").value("Jane"));
}
@Test
@DisplayName("GET /customers/{id} returns 404 when not found")
void getCustomerByIdNotFound() throws Exception {
when(customerService.getCustomerById(99L)).thenReturn(Optional.empty());
mockMvc.perform(get("/customers/99"))
.andExpect(status().isNotFound());
}
@Test
@DisplayName("POST /customers creates a customer")
void createCustomer() throws Exception {
Customer customer = new Customer(1L, "Alice", "Wonderland");
when(customerService.createCustomer(any(Customer.class))).thenReturn(customer);
String json = "{\"id\":1,\"firstName\":\"Alice\",\"lastName\":\"Wonderland\"}";
mockMvc.perform(post("/customers")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isCreated());
}
@Test
@DisplayName("PUT /customers/{id} updates a customer")
void updateCustomer() throws Exception {
Customer customer = new Customer(1L, "Bob", "Builder");
when(customerService.updateCustomer(anyLong(), any(Customer.class))).thenReturn(customer);
String json = "{\"id\":1,\"firstName\":\"Bob\",\"lastName\":\"Builder\"}";
mockMvc.perform(put("/customers/1")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isOk());
}
@Test
@DisplayName("DELETE /customers/{id} deletes a customer")
void deleteCustomer() throws Exception {
mockMvc.perform(delete("/customers/1"))
.andExpect(status().isNoContent());
}
}
This test is successful. When you generate the mutation test results using mvn verify and check the report in the target/pit-reports directory, you notice that this test has a line coverage of 100% and a mutation coverage of 93% which is quite good. Generating this test with the Claude models of Anthropic, results in similar numbers.

7. Commands
Qwen Code supports commands. This is a convenient way of interacting with the model.
When using the /init command, Qwen Code analyses your repository and creates a QWEN.md file in your repository with project specific information. Executing this command for this repository, results in the following QWEN.md file. Generating this file took a couple of minutes, but the result is really good.
# QWEN.md
## Project Overview
This repository is a **Spring Boot** application written in **Java 21** that exposes a simple **Customer Management API**. It demonstrates:
* RESTful endpoints generated from an OpenAPI specification (`src/main/resources/static/customers.yaml`).
* JOOQ code‑generation for type‑safe database access.
* Liquibase migrations for schema creation.
* Testcontainers integration for integration tests.
* PIT mutation testing.
The application uses a PostgreSQL database (Docker image `postgres:17-alpine`) defined in `compose.yaml`.
## Directory Structure
```
src/
├─ main/
│ ├─ java/com/mydeveloperplanet/myaicodeprojectplanet
│ │ ├─ controller
│ │ ├─ model
│ │ ├─ repository
│ │ ├─ service
│ │ └─ MyAiCodeProjectPlanetApplication.java
│ ├─ resources/
│ │ ├─ db/changelog/migration
│ │ ├─ static/customers.yaml
│ │ └─ application.properties
└─ test/
└─ java/com/mydeveloperplanet/myaicodeprojectplanet/controller
```
## Build & Run
The project uses **Maven**. The following commands are the typical workflow:
| Action | Command |
|--------|---------|
| Clean & compile | `./mvnw clean compile` |
| Run the application | `./mvnw spring-boot:run` |
| Run tests | `./mvnw test` |
| Generate JOOQ sources | `./mvnw generate-sources` |
| Run mutation tests | `./mvnw test-compile exec:java -Dexec.mainClass=org.pitest.mutationtest.MutationTest` |
> **Note**: The wrapper scripts `mvnw` and `mvnw.cmd` are provided for cross‑platform builds.
## Docker Compose
A `compose.yaml` file is included to spin up a PostgreSQL instance for local development:
```bash
docker compose up -d
```
The database is exposed on port **5432** and is automatically connected via Spring Boot’s `spring-boot-docker-compose` dependency.
## Testing
Unit tests are located under `src/test/java`. They use **JUnit 5** and **Spring MockMvc**. Integration tests can be added using Testcontainers.
Run all tests with:
```bash
./mvnw test
```
## Conventions & Style
* **Java** code follows the standard Spring Boot conventions.
* **Naming**: Packages use lower‑case, classes use PascalCase.
* **Configuration**: All runtime properties are in `application.properties`.
* **OpenAPI**: The API contract is defined in `customers.yaml` and used by the OpenAPI generator plugin.
* **Database**: Liquibase changelogs are in `src/main/resources/db/changelog`.
## Extending the Project
* Add new entities by creating a JOOQ table, updating the Liquibase changelog, and generating JOOQ sources.
* Add new REST endpoints by implementing the corresponding OpenAPI interface and wiring it into the controller.
* Add tests under the appropriate package.
---
*Generated by Qwen Code on 2025‑12‑31.*
Using the exclamation mark !, you can execute shell commands. However, aliases are not recognized. When using ll for example, the command cannot be executed. You have to use ls -la instead. Another drawback is that it executes quite slowly.
A really nice feature is the option to create custom commands with predefined prompts. Very useful when you want to use prompts repetitively, and you can share them easily with someone else.
Create in the .qwen directory of your home directory a directory commands. Using extra directories inside this commands directory, you can create namespaces. As an example, the following directory tree.
~/.qwen/commands$ tree
.
├── general
│ ├── explain.toml
│ └── javadoc.toml
├── review
│ ├── extended.toml
│ └── simple.toml
└── test
├── controller.toml
├── integration.toml
├── repositoryjooq.toml
└── service.toml
The controller.toml file, contains the prompt you used for creating the test.
description = "Create a test for a Spring Boot Controller"
prompt = """
Write a unit test for this code code using JUnit.
Use WebMvcTest.
Use MockMvc.
Use AssertJ assertions.
The previous prompt can now be reduced to:
@src/main/java/com/mydeveloperplanet/myaicodeprojectplanet/controller/CustomersController.java
/test:controller
8. MCP
With MCP (Model Context Protocol) servers, you can enhance the capabilities of the model.
The configuration of an MCP server can be added to the settings.json file. The Context7 MCP server can be added as follows.
"mcpServers": {
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"],
"timeout": 15000
}
}
Check whether the MCP server is configured correctly.
$ qwen mcp list
Configured MCP servers:
✓ context7: npx -y @upstash/context7-mcp (stdio) - Connected
The nice thing of this configuration is that you are also able to indicate by means of the trust parameter whether the tool confirmation may be skipped. Also, you are able to include and exclude certain tools with the help of the includeTools and excludeTools parameters.
Remove the previously created test, add the CustomersController and use the following prompt.
/test:controller create the test in this repository, i am using spring boot 3.4, use context7 to retrieve uptodate information
This should result in invoking the Context7 MCP server and as a result MockBean should not be used in the test, but MockitoBean should be used instead.
However, the Context7 MCP server is not invoked. Several other prompts are used, but it seems to be very hard to force the model to use the MCP server. The same prompt using DevoxxGenie does invoke the MCP server, so it is unlikely that this is caused by the model. An issue was created for this purpose but it seems that Qwen Code and MCP are not very good friends yet.
9. Conclusion
Qwen Code offers quite some nice features. There is a lot more to discover, but the first impressions are good. It is also good to experiment with other AI coding assistants now and then, in order to see how they compare to the ones you are using.
Discover more from
Subscribe to get the latest posts sent to your email.
