Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Sunday, 3 August 2025

Code Formatting Control in Java: Using @formatter:off and @formatter:on

When working in Java, most developers use automatic code formatters in their IDEs like IntelliJ IDEA or Eclipse. These formatters help to keep code clean and consistent, but sometimes, they change things you didn’t want them to.

Have you ever written a nicely aligned comment, an ASCII diagram, or a table in Javadoc, only to see it get messed up by your formatter? That's where @formatter:off and @formatter:on come in.

 

These special comments let you turn off the auto-formatter temporarily, so you can keep your code or documentation exactly how you want it. Once you're done, you turn formatting back on. It’s like telling your IDE, “Hands off — I’ve got this part.”

 

In this post, we’ll learn what these formatter tags are, when and how to use them, and how they’re supported in popular tools like IntelliJ, Eclipse, and Spotless. If you’ve ever struggled with keeping your code layout, this guide is for you.

 

1. Introduction

Most developers use an automatic code formatter in their code editor or IDE (like IntelliJ IDEA or Eclipse). A code formatter is a tool that automatically cleans up your code’s layout, like adding proper indentation, fixing spacing, aligning braces, and so on.

 

This helps:

·      Make your code easier to read.

·      Keep everyone on a team writing code in a consistent style.

·      Save time by not having to format everything by hand.

 

But sometimes, the formatter can be too aggressive, meaning it changes parts of your code that you want to keep exactly as they are.

 

1.1 Common Problems That Happen

 

a. ASCII Diagrams or Visual Layouts

 

Example

public class FileProcessingPipeline {

    public void runPipeline() {

        // ┌────────────────┐
        // │  Input Source  │
        // └──────┬─────────┘
        //        │
        //        ▼
        // ┌────────────────┐
        // │   Preprocessor │
        // └──────┬─────────┘
        //        │
        //        ▼
        // ┌────────────────┐
        // │   Processor    │
        // └──────┬─────────┘
        //        │
        //        ▼
        // ┌────────────────┐
        // │   Formatter    │
        // └──────┬─────────┘
        //        │
        //        ▼
        // ┌────────────────┐
        // │ Output Writer  │
        // └────────────────┘

        // Steps would be called here in order
    }

   
}

After code formatting it change like below.

 


b. Tables in Javadoc

If you create a table in your Javadoc using text, the formatter might shift lines around and ruin the alignment:

/**
 * User Information Table:
 *
 * +------------+-----+-------------+----------------------+------------------+
 * | Name       | Age | Occupation  | Email                | Registered Date  |
 * +------------+-----+-------------+----------------------+------------------+
 * | Alice      | 28  | Engineer    | alice@example.com    | 2021-03-15       |
 * | Bob        | 35  | Designer    | bob.design@example.com | 2020-11-30     |
 * | Charlie    | 22  | Student     | charlie@univ.edu     | 2023-01-05       |
 * | Diana      | 41  | Manager     | diana@corp.com       | 2019-07-19       |
 * +------------+-----+-------------+----------------------+------------------+
 *
 * <p>
 * Columns:
 * <ul>
 * <li><b>Name</b> - Full name of the user</li>
 * <li><b>Age</b> - Current age</li>
 * <li><b>Occupation</b> - Current profession</li>
 * <li><b>Email</b> - Contact email address</li>
 * <li><b>Registered Date</b> - Date the user registered in the system</li>
 * </ul>
 */

After formatting it change like below.

 


c. Legacy Code

Sometimes you’re working with old code written in a very specific format. Changing it could cause bugs or confusion. A formatter might reformat it without understanding why it was written that way.

 

d. Aligned Comments

You might write comments like this

    int count    = 10;  // number of items
    int maxLimit = 100; // maximum allowed

A formatter might remove the spacing before the comments, making them harder to read in a block.


While formatters try to make your code clean and consistent, they don’t always understand the meaning or importance of certain formatting. This can be frustrating when you’ve spent time making something readable, only for the formatter to undo your work.

 

2. The Hidden Gems: @formatter:off and @formatter:on

@formatter:off and @formatter:on are special comments (not actual Java code or annotations) that tell your IDE or code formatter to stop and start formatting automatically.

 

They are not part of the Java language itself, they are just instructions to the code formatting tool, like saying:

 

“Hey formatter, don’t touch the next few lines.”

“Okay formatter, you can start again now.”

 

This is very helpful when you want to keep specific formatting exactly as you typed it, and prevent the formatter from changing it.

 

Even though they look like annotations (@something), they are not Java annotations. Instead, they are just inline comments that some IDEs recognize.

 

Example

 

// @formatter:off

 

Which tools support these instructions?

These formatter tags are recognized by:

·      Eclipse (out of the box)

·      IntelliJ IDEA (you need to enable it)

·      Spotless (depends on the formatter engine you use, Google Java Format ignores it, but Eclipse formatters might support it)

 

Here’s a simple code snippet showing how to use them:

// @formatter:off
int valueA    = 10;  // aligned comment
int valueLong = 20;  // aligned comment
int shortV    = 30;  // aligned comment
// @formatter:on

Without @formatter:off, your formatter might collapse everything like this:

int valueA = 10; // aligned comment
int valueLong = 20; // aligned comment
int shortV = 30; // aligned comment

 

3. Best Practices for Using @formatter:off and @formatter:on

Using @formatter:off and @formatter:on can be very helpful, but like any powerful tool, it's important to use it carefully and responsibly. Here are some best practices to follow.

 

3.1 Don’t Overuse It

Just because you can turn off the formatter doesn't mean you should do it everywhere. Auto-formatting helps keep your code:

 

·      Clean

·      Consistent

·      Easy to read, especially in large teams

 

If you turn off formatting too often, your code might start looking messy or inconsistent, which defeats the purpose of using a formatter in the first place.

 

3.2. Use It Only When You Really Need To

Use @formatter:off only around specific blocks where you absolutely need to preserve the exact layout, such as:

 

·      Javadoc tables

·      ASCII diagrams

·      Aligned comments

·      Legacy code where formatting affects logic or readability.

 

Example

// @formatter:off
/**
 * +----------+--------------+
 * | Name       | Role       |
 * +------------+------------+
 * | Ram        | Developer  |
 * | Krishna    | Designer   |
 */
// @formatter:on

 

3.3 Leave a Comment Explaining Why

If you turn off formatting for a section, it's a good habit to leave a short comment explaining why. This helps other developers (or future you!) understand the reason.

 

Example

// @formatter:off
// Table format must be preserved for documentation readability
/**
 * +----------+------------+
 * | Column A | Column B   |
 * +----------+------------+
 */
// @formatter:on

 

4. Final Thoughts

4.1 When Should You Use @formatter:off?

Use this feature only when auto-formatting makes your code harder to read, and you have a good reason to keep your own formatting.

 

Examples include:

·      Tables or diagrams in Javadoc

·      Aligned comments for better visual structure

·      Preserving formatting in legacy code or custom layouts

 

You shouldn't use it just because you don't like the formatter's style, formatters help teams write consistent code. But when readability truly depends on your layout, that’s the right time to reach for @formatter:off.

 

4.2 Your Code, Your Way - Without Fighting the Formatter

Instead of turning off formatting for your entire file or disabling your formatter altogether, @formatter:off and @formatter:on let you:

 

·      Keep control where it matters

·      Let the formatter work everywhere else

 

This means your code stays mostly clean and consistent, but also preserves your intent in areas that require special formatting.

 

In summary, think of @formatter:off as a temporary "pause" button for formatting, use it only when needed, and your code will be both clean and human-friendly.

 

  

You may like

Miscellaneous

Programmatically import certificate to cacerts file in Java

Command to check CPU temperature in Mac

Quick guide to Java DecimalFormat class

Compress and decompress a string in Java

Discover and load the implementations of a service using ServiceLoader in Java

Tuesday, 8 July 2025

Extract Structured Data into POJOs Using LangChain4j with JSON Mode

Do you want to learn how to leverage LangChain4j's @AiService with JSON mode to extract structured data from unstructured natural language text directly into custom Java POJOs. This post demonstrates how to annotate your data model and interface to guide the LLM in mapping fields correctly using @Description, along with the importance of JSON mode for reliable parsing.

 

When working with unstructured text data, it's common to want structured outputs like Java objects. With LangChain4j, you can easily define POJOs (Plain Old Java Objects) and let the LLM extract information into these objects directly, especially powerful when using JSON mode to ensure predictable, valid JSON responses.

 

Define Your POJOs with Helpful Descriptions.

public class Employee {
  @Description("First name of an Employee")
  private String firstName;

  @Description("Last name of an Employee")
  private String lastName;

  @Description("Employee Birth date, if unable to retrieve the valid birth date keep it as null")
  private LocalDate birthDate;

  @Description("Employee Address")
  private Address address;
}

@Description("Specify an Employee Address")
class Address {
  private String street;
  private Integer streetNumber;
  private String city;
}

These @Description annotations help the LLM understand how to populate each field.

 

Create the AI Service Interface:

 

public interface EmployeeExtractor {
  @UserMessage("Extract information about an employee from the following information:\n{{description}}")
  Employee extractPersonFrom(@V("description") String description);
}

Make sure JSON mode is enabled in your chat model config (depending on the LangChain4j version and provider, e.g., OpenAI):

EmployeeExtractor empExtractor = AiServices.create(EmployeeExtractor.class, chatModel);

 

Provide a Natural Language Description.

String description = "In the crisp winter morning of January 2010, just as the city stirred to life after New Year celebrations, Rajesh Nair stepped into the next chapter of his professional journey. Carrying a humble backpack and a quiet determination, Rajesh joined as a software engineer at Sundaram Tech Solutions. His joining formalities were completed at Block B, 6th Floor, Sriram IT Park, Hinjewadi Phase 2, Pune — an address that would soon become the backdrop of countless hours of coding, coffee breaks, and career-defining milestones.";

Employee emp = empExtractor.extractPersonFrom(description);

 

Why to use JSON Mode?

When you enable JSON mode, LangChain4j instructs the LLM to strictly return a valid JSON that can be directly parsed into your POJO. This ensures better reliability and consistency in extraction use cases like:

 

·      Resume parsing

·      News summarization

·      Product review analysis

·      Entity extraction

 

How to Enable JSON Mode for Different Providers?

Below are the code snippets and explanations for enabling JSON mode with various popular model providers.

 

For newer OpenAI models (e.g., gpt-4o-mini, gpt-4o-2024-08-06) that support structured output:

OpenAiChatModel.builder()
    .apiKey("YOUR_API_KEY")
    .modelName("gpt-4o-2024-08-06")
    .supportedCapabilities(SupportedCapabilities.RESPONSE_FORMAT_JSON_SCHEMA)
    .strictJsonSchema(true) // Enforces strict validation
    .build();

These models natively support schema-guided generation, improving JSON consistency.

 

For older OpenAI models (e.g., gpt-3.5-turbo, gpt-4):

OpenAiChatModel.builder()
    .apiKey("YOUR_API_KEY")
    .modelName("gpt-3.5-turbo")
    .responseFormat("json_object") // Prompts the model to respond in JSON
    .build();

 

While not as strict as schema-guided outputs, this still helps steer the model toward structured results.

 

Azure OpenAI

AzureOpenAiChatModel.builder()
    .apiKey("YOUR_AZURE_KEY")
    .endpoint("YOUR_ENDPOINT")
    .deploymentName("gpt-deployment")
    .responseFormat(new ChatCompletionsJsonResponseFormat())
    .build();

Ensures structured responses when using Azure-hosted OpenAI models.

 

Google Vertex AI Gemini

Basic JSON response configuration:

VertexAiGeminiChatModel.builder()
    .projectId("your-project")
    .location("us-central1")
    .responseMimeType("application/json")
    .build();

 

With schema generated from a Java class:

VertexAiGeminiChatModel.builder()
    .responseSchema(SchemaHelper.fromClass(Employee.class))
    .build();

 

Using a custom JSON schema:

VertexAiGeminiChatModel.builder()
    .responseSchema(Schema.builder()
        .title("Employee")
        .type(SchemaType.OBJECT)
        // add your properties here
        .build())
    .build();

 

Google AI Gemini

Basic JSON format setup:

 

GoogleAiGeminiChatModel.builder()
    .responseFormat(ResponseFormat.JSON)
    .build();

 

With schema from a Java class:

GoogleAiGeminiChatModel.builder()
    .responseFormat(ResponseFormat.builder()
        .type(ResponseFormat.Type.JSON)
        .jsonSchema(JsonSchemas.jsonSchemaFrom(Employee.class).get())
        .build())
    .build();

 

Or define your own JSON schema manually:

GoogleAiGeminiChatModel.builder()
    .responseFormat(ResponseFormat.builder()
        .type(ResponseFormat.Type.JSON)
        .jsonSchema(JsonSchema.builder()
            // define schema fields here
            .build())
        .build())
    .build();

 

Mistral AI

MistralAiChatModel.builder()
    .apiKey("YOUR_API_KEY")
    .responseFormat(MistralAiResponseFormatType.JSON_OBJECT)
    .build();

 

Enables deterministic and parseable JSON responses from Mistral models.

 

Ollama

 

OllamaChatModel.builder()
    .responseFormat(ResponseFormat.JSON)
    .build();

Ensure your Ollama-hosted models return valid JSON responses for structured parsing.

 

If the model you’re using does not natively support JSON mode, consider the following:

 

·      Prompt Engineering: Manually instruct the model in your prompt to return only valid JSON.

·      Lower the Temperature: Reduces randomness, making responses more predictable and structured.

·      Post-Processing: Validate and clean the output using JSON parsers or fallback handling logic.

 

 

Follow below step-by-step procedure to build a working application.

 

Step 1: Create new maven project custom-pojo-as-return-type.

 

Step 2: Update pom.xml with maven dependencies.

 

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sample.app</groupId>
  <artifactId>custom-pojo-as-return-type</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <properties>
    <maven.compiler.source>21</maven.compiler.source>
    <maven.compiler.target>21</maven.compiler.target>
    <java.version>21</java.version>
  </properties>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.10</version>
  </parent>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-bom</artifactId>
        <version>1.0.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j-spring-boot-starter</artifactId>
    </dependency>


    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
    </dependency>

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

    <dependency>
      <groupId>org.springdoc</groupId>
      <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
      <version>2.6.0</version>
    </dependency>

    <!--
    https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api -->
    <dependency>
      <groupId>jakarta.validation</groupId>
      <artifactId>jakarta.validation-api</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal> <!-- Important -->
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

 

Step 3: Define Employee and Address POJOs.

 

Address.java

 

package com.sample.app.dto;

import dev.langchain4j.model.output.structured.Description;

//you can add an optional description to help an LLM have a better understanding
@Description("Specify an Employee Address")
class Address {
  private String street;
  private Integer streetNumber;
  private String city;

  public String getStreet() {
    return street;
  }

  public void setStreet(String street) {
    this.street = street;
  }

  public Integer getStreetNumber() {
    return streetNumber;
  }

  public void setStreetNumber(Integer streetNumber) {
    this.streetNumber = streetNumber;
  }

  public String getCity() {
    return city;
  }

  public void setCity(String city) {
    this.city = city;
  }

}

 

Employee.java

package com.sample.app.dto;

import java.time.LocalDate;

import dev.langchain4j.model.output.structured.Description;

public class Employee {
  // We can add additional details to help LLM have a better understanding.
  @Description("First name of an Employee")
  private String firstName;
  
  @Description("Last name of an Employee")
  private String lastName;
  
  @Description("Employee Birth date, if unable to retrive the valid brith date keep it as null")
  private LocalDate birthDate;
  
  @Description("Employee Address")
  private  Address address;

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public LocalDate getBirthDate() {
    return birthDate;
  }

  public void setBirthDate(LocalDate birthDate) {
    this.birthDate = birthDate;
  }

  public Address getAddress() {
    return address;
  }

  public void setAddress(Address address) {
    this.address = address;
  }

}

 

Step 4: Define EmployeeExtractor interface.

 

EmployeeExtractor.java

 

package com.sample.app.interfaces;

import com.sample.app.dto.Employee;

import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface EmployeeExtractor {
  @UserMessage("Extract information about an employee from following information.\n{{description}}")
  Employee extractPersonFrom(@V("description") String description);
}

Step 5: Define LangchainConfig and SwaggerConfig classes.

LangchainConfig.java

 

package com.sample.app.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import dev.langchain4j.http.client.spring.restclient.SpringRestClientBuilderFactory;
import dev.langchain4j.model.chat.request.ResponseFormat;
import dev.langchain4j.model.ollama.OllamaChatModel;

@Configuration
public class LangchainConfig {

  @Bean
  public OllamaChatModel ollamaLanguageModel() {
    return OllamaChatModel.builder().baseUrl("http://localhost:11434").modelName("llama3.2")
        .responseFormat(ResponseFormat.JSON).temperature(0.2)
        .logRequests(true)
        .httpClientBuilder(new SpringRestClientBuilderFactory().create()) // explicitly use Spring's HTTP client
        .build();
  }
}

SwaggerConfig.java

package com.sample.app.config;

import org.springframework.context.annotation.Configuration;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;

@Configuration
@OpenAPIDefinition(info = @Info(title = "Chat service Application", version = "v1"))
public class SwaggerConfig {

}

 

Step 6: Define EmployeeExtractionService class.

 

EmployeeExtractionService.java 

package com.sample.app.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.sample.app.dto.Employee;
import com.sample.app.interfaces.EmployeeExtractor;

import dev.langchain4j.model.ollama.OllamaChatModel;
import dev.langchain4j.service.AiServices;

@Service
public class EmployeeExtractionService {

  @Autowired
  private OllamaChatModel chatModel;

  public Employee getEmployee(String description) {
    EmployeeExtractor empExtractor = AiServices.create(EmployeeExtractor.class, chatModel);
    return empExtractor.extractPersonFrom(description);
  }

}

 

Step 7: Define EmployeeController class.

 

EmployeeController.java

package com.sample.app.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.sample.app.dto.Employee;
import com.sample.app.service.ExmployeeExtractionService;

import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;

@RestController
@RequestMapping("/api/employee-extractor")
@CrossOrigin("*")
@Tag(name = "Employee Controller", description = "This section contains APIs related to Employee APIs Powered by Ollama")
public class EmployeeController {

  @Autowired
  private ExmployeeExtractionService employeeExtractionService;

  @PostMapping
  public Employee chat(@RequestBody @Valid RequestPayload requestPayload) {
    return employeeExtractionService.getEmployee(requestPayload.getDescription());
  }

  private static class RequestPayload {
    @NotEmpty
    private String description;

    public String getDescription() {
      return description;
    }

    public void setDescription(String description) {
      this.description = description;
    }

  }
}

Step 8: Define main application class.

 

App.java

package com.sample.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {

  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }

}

Build the project

Navigate to the project root directory where pom.xml is located and execute following command to generate the artifact.

mvn clean install

 

Upon successful execution of the command, you can see custom-pojo-as-return-type-0.0.1-SNAPSHOT.jar file in the target folder.

$ ls ./target/
classes             generated-test-sources
custom-pojo-as-return-type-0.0.1-SNAPSHOT.jar   maven-archiver
custom-pojo-as-return-type-0.0.1-SNAPSHOT.jar.original  maven-status
generated-sources         test-classes

 

Run the Application

Execute following command to run the application

 

java -jar ./target/custom-pojo-as-return-type-0.0.1-SNAPSHOT.jar --server.port=1235

Open following url in the browser.

http://localhost:1235/swagger-ui/index.html

 

Execute the API /api/employee-extractor with below payload.

{
  "description": "In the crisp winter morning of January 2010, just as the city stirred to life after New Year celebrations, Rajesh Nair stepped into the next chapter of his professional journey. Carrying a humble backpack and a quiet determination, Rajesh joined as a software engineer at Sundaram Tech Solutions. His joining formalities were completed at Block B, 6th Floor, Sriram IT Park, Hinjewadi Phase 2, Pune — an address that would soon become the backdrop of countless hours of coding, coffee breaks, and career-defining milestones. As he walked past buzzing workstations and glass cabins filled with ideas, the hum of keyboards and soft chimes of emails felt like the rhythm of possibility. With every step, Rajesh began not just a job, but a journey marked by passion, perseverance, and purpose."
}

You will see following details.

{
  "firstName": "Rajesh",
  "lastName": "Nair",
  "birthDate": null,
  "address": {
    "street": "Sriram IT Park",
    "streetNumber": 6,
    "city": "Hinjewadi Phase 2, Pune"
  }
}

You can download the application from this link

 

 

Previous                                                    Next                                                    Home