Thursday, April 9, 2026

PreparedStatement Interface in Java-JDBC

In the post Statement interface in Java we have already seen how you can create a Statement using Connection object and execute SQL statements. However, the Statement interface has a major limitation; it only works with static SQL queries and offers no direct way to pass parameters. Developers often resorted to string concatenation or StringBuilder to inject values, but this approach is error‑prone and vulnerable to SQL injection attacks.

To solve these issues, JDBC introduced the PreparedStatement Interface in Java, a sub‑interface of Statement designed for parameterized queries. With PreparedStatement, you can safely bind values to placeholders (?) in your SQL, making your code more secure, readable, and efficient. In this post we'll see how to use PreparedStatement in JDBC with examples

Obtaining JDBC PreparedStatement object

You can create a PreparedStatement object by calling the prepareStatement() method of the Connection class.

PreparedStatement preparedStatement = connection.prepareStatement(sql);

Advantages of using PreparedStatement in JDBC

  • Parameterized Queries: You can reuse the same SQL statement with different parameter values, reducing duplication.
  • Efficiency: Unlike Statement object, PreparedStatement is given the SQL statement when it is created. So the SQL is sent to the DB right away where it is already compiled. When you come to execute() method to actually execute the SQL that SQL is pre-compiled making it more efficient for repeated executions.
  • Security: By separating SQL logic from parameter values, PreparedStatement prevents SQL injection attacks.
  • Cleaner Syntax: No need for multiple break statements or messy string concatenation, parameters are set using methods like setInt(), setString(), etc.

Java PreparedStatement Example

Let’s see an example using PreparedStatement in JDBC. DB used here is MySql, schema is netjs and table is employee with columns id, age and name, where id is auto-generated.

In the code there are methods for insert, update, delete and select from the table.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JDBCPrepStmt {
  public static void main(String[] args) {
    Connection connection = null;
    try {
      // Loading driver
      Class.forName("com.mysql.jdbc.Driver");
    
      // Creating connection
      connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/netjs", 
                         "root", "admin");
      JDBCPrepStmt prep = new JDBCPrepStmt();
      prep.insertEmployee(connection, "Kate", 24);
      prep.updateEmployee(connection, 22, 30);
      prep.displayEmployee(connection, 22);
    
      //prep.deleteEmployee(connection, 24);
    } catch (ClassNotFoundException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (SQLException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }finally{
      if(connection != null){
        //closing connection 
        try {
          connection.close();
        } catch (SQLException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      } // if condition
    }// finally
  }
 
  // Method to insert
  private void insertEmployee(Connection connection, String name, int age) 
        throws SQLException{
    String insertSQL = "Insert into employee (name, age) values (?, ?)";
    PreparedStatement prepStmt = null;
    try {
      prepStmt = connection.prepareStatement(insertSQL);
      prepStmt.setString(1, name);
      prepStmt.setInt(2, age);
      int count = prepStmt.executeUpdate();
      System.out.println("Count of rows inserted " + count);
    }finally{
      if(prepStmt != null){
        prepStmt.close();
      }
    }
  }
 
 // Method to update
 private void updateEmployee(Connection connection, int id, int age) throws SQLException{
  String updateSQL = "Update employee set age = ? where id = ?";
  PreparedStatement prepStmt = null;
  try {
   prepStmt = connection.prepareStatement(updateSQL);
   prepStmt.setInt(1, age);
   prepStmt.setInt(2, id);
   int count = prepStmt.executeUpdate();
   System.out.println("Count of rows updated " + count);
  }finally{
    if(prepStmt != null){
     prepStmt.close();
    }
  }
 }
 
 // Method to delete
 private void deleteEmployee(Connection connection, int id) throws SQLException {
  String deleteSQL = "Delete from employee where id = ?";
  PreparedStatement prepStmt = null;
  try {
   prepStmt = connection.prepareStatement(deleteSQL);
   prepStmt.setInt(1, id);
   int count = prepStmt.executeUpdate();
   System.out.println("Count of rows deleted " + count);
  }finally{
    if(prepStmt != null){
     prepStmt.close();
    }
  }
 }
 

 // Method to retrieve
 private void displayEmployee(Connection connection, int id) throws SQLException{
  String selectSQL = "Select * from employee where id = ?";
  PreparedStatement prepStmt = null;
  try {
   prepStmt = connection.prepareStatement(selectSQL);
   prepStmt.setInt(1, id);
   ResultSet rs = prepStmt.executeQuery();
   while(rs.next()){
     System.out.println("id : " + rs.getInt("id") + " Name : " 
                   + rs.getString("name") + " Age : " + rs.getInt("age")); 
   }
  }finally{
    if(prepStmt != null){
     prepStmt.close();
    }
  }
 }
 
}

Points to note here:

Taking this example as reference let’s go through some of the points you will have to keep in mind when using PreparedStatement in JDBC.

  1. Parameterized statement– In the example you can see that all the SQL statements are parameterized and '?' is used as a placeholder in parameterized statements. For example-
    String insertSQL = "Insert into employee (name, age) values (?, ?)";
    
  2. Setter methods– Values for these placeholders are provided through setter methods. PreparedStatement has various setter methods for different data types i.e. setInt(), setString(), setDate() etc.

    General form of the setter method-

    setXXX(int parameterIndex, value)
    

    Here parameterIndex is the index of the parameter in the statement, index starts from 1. For example-

    String insertSQL = "Insert into employee (name, age) values (?, ?)";
    

    For this sql, where the first parameter is String (name) and second parameter is of type int (age), you need to set the parameters on the PreparedStatement object as follows-

    prepStmt.setString(1, name);
    prepStmt.setInt(2, age);
    
  3. Executing PreparedStatement objects– You can use execute methods for executing the queries.
    1. boolean execute()- Executes the SQL statement in this PreparedStatement object, (it can be any kind of SQL query), which may return multiple results.
      Returns a boolean which is true if the first result is a ResultSet object; false if it is an update count or there are no results.
    2. ResultSet executeQuery(String sql)- Executes the SQL statement in this PreparedStatement object, which returns a single ResultSet object. If you want to execute a Select SQL query which returns results you should use this method.
    3. int executeUpdate()- Executes the SQL statement in this PreparedStatement object, which may be an INSERT, UPDATE, or DELETE statement or an SQL statement that returns nothing, such as an SQL DDL statement.
      Returns an int denoting either the row count for the rows that are inserted, deleted, updated or returns 0 if nothing is returned.
  4. That's all for this topic PreparedStatement Interface in Java-JDBC. If you have any doubt or any suggestions to make please drop a comment. Thanks!

    >>>Return to Java Advanced Tutorial Page


    Related Topics

    1. JDBC Tutorial - Java JDBC Overview
    2. ResultSet Interface in Java-JDBC
    3. Transaction Management in Java-JDBC
    4. Connection Pooling Using C3P0 in Java
    5. Data Access in Spring Framework

    You may also like-

    1. Ternary Operator in Java With Examples
    2. Race Condition in Java Multi-Threading
    3. Switch Expressions in Java
    4. Spliterator in Java
    5. Lambda Expressions in Java
    6. Type erasure in Java Generics
    7. Spring NamedParameterJdbcTemplate Insert, Update And Delete Example
    8. Messages in LangChain

Switch Expressions in Java

Introduced as a preview in Java 12 and standardized in Java 14, switch expressions in Java extends the traditional switch statement by allowing it to be used both as a statement and as an expression. This enhancement makes code more concise, eliminates boilerplate break statements, and reduces the risk of fall‑through errors.


Java Switch expressions

A new form of switch label, written "case L ->" is one of the most notable changes that allows the code to the right of the label to execute only if the label is matched. Unlike the old switch statement, this modern approach ensures clarity and avoids accidental fall‑through.

We’ll try to understand this switch expression with an example, initially let’s use traditional switch statement to write a conditional switch-case branch and then use switch expression to see how it simplifies it.

For example if you want to determine which quarter a given month belongs to then you can group three case statements where break statement is used with only the last one in the group.

public class SwitchCaseDemo {

 public static void main(String[] args) {
  int month = 4;  
  switch(month){
   case 1:    
   case 2:    
   case 3: System.out.println("Quarter 1"); 
     break;
   
   case 4:   
   case 5:     
   case 6: System.out.println("Quarter 2"); 
     break;
   
   case 7:   
   case 8:  
   case 9: System.out.println("Quarter 3"); 
     break;
   
   case 10:     
   case 11:   
   case 12: System.out.println("Quarter 4"); 
      break;
   
   default: System.out.println("Invalid month value passed");
  }
 }
}

Consider some of the pain areas here-

  1. Repetition of cases: Even if multiple cases map to the same outcome, you still need to write them on separate lines, which bloats the code.
  2. Verbose break usage: Every branch required an explicit break to prevent fall‑through, making the code unnecessarily verbose.
  3. Accidental fall‑throughs: Missing a break statement results in an accidental fall-through.

Now let’s write the same example using Java switch expressions.

public class SwitchCaseDemo {

  public static void main(String[] args) {
    int month = 4;        
    switch(month){
      case 1, 2, 3 -> System.out.println("Quarter 1");         

      case 4, 5, 6 -> System.out.println("Quarter 2");     
  
      case 7, 8, 9 -> System.out.println("Quarter 3");             
       
      case 10, 11, 12 -> System.out.println("Quarter 4");              
      
      default -> System.out.println("Invalid month value passed");
    }
  }
}

Note the changes here-

  1. Multiple case labels can be grouped together now.
  2. Break statement is not required any more. If a label is matched, then only the expression or statement to the right of an arrow label is executed; there is no fall through.
  3. This new switch form uses the lambda-style syntax introduced in Java 8 consisting of the arrow between the label and the code that returns a value.

The new kind of case label has the following form

case label_1, label_2, ..., label_n -> expression;|throw-statement;|block

Java 12 onward you can use colon syntax (:) too with multiple case labels but in that case break statement has to be used to avoid fall-through.

public class SwitchCaseDemo {

 public static void main(String[] args) {
  int month = 4;  
  switch(month){
   case 1, 2, 3: System.out.println("Quarter 1"); 
         break;

   case 4, 5, 6: System.out.println("Quarter 2");  
       break;
   case 7, 8, 9: System.out.println("Quarter 3");    
       break;
   case 10, 11, 12: System.out.println("Quarter 4");     
       break;
   default : System.out.println("Invalid month value passed");
  }
 }
}

Why is it called Switch expression

Now the more pertinent question is why this new feature is called switch expression in Java. As you must be knowing; Statements are essentially "actions" that have to be executed. Expressions, however, are "requests" that evaluates to a value. Same difference applies to switch statement and switch expressions too.

  • Traditional switch statement: Executes blocks of code based on matching cases, but does not itself yield a value.
  • Switch expression: Evaluates to a value that can be directly assigned or returned, making it usable in places where a value is required.

Here is an example showing returning a value from a traditional switch statement.

public class SwitchCaseDemo {
 public static void main(String[] args) {
  System.out.println(getMessage("Start"));
 }
 private static String getMessage(String event) {
  String message = "No event to log";
  switch (event) {
    case "Start":
      message = "User started the event";
      break;
    case "Stop":
      message = "User stopped the event";
      break;
  }
  return message;
 }
}

Output

User started the event

Same thing with Java Switch expressions. Since expression itself produces a value so it can be assigned to a variable directly.

public class SwitchCaseDemo {

 public static void main(String[] args) {
  System.out.println(getMessage("Start"));
 }
 private static String getMessage(String event) {
  var msg = switch (event) {
    case "Start" ->  "User started the event";
    case "Stop" -> "User stopped the event";
    default -> "No event to log";
  };
  return msg;
 }
}

Output

User started the event

Using yield statement with Switch expression

If you want to use colon syntax then you can assign the value directly using yield statement. The yield statement takes one argument, which is the value that the case label produces in a switch expression.

public class SwitchCaseDemo {

  public static void main(String[] args) {
    System.out.println(getMessage("Stop"));
  }
	 
  private static String getMessage(String event) {
    var msg = switch (event) {
      case "Start": 
        yield "User started the event";
      case "Stop": 
        yield "User stopped the event";
      default: 
        yield "No event to log";
    };
    return msg;
  }
}

Writing statement blocks

If you need to have multiple statements with in a case you can use a statement block enclosed in curly braces. Specify the value that the case label produces with the yield statement.

public class SwitchCaseDemo {

  public static void main(String[] args) {
    int month = 4;        
    var value = switch(month){
      case 1, 2, 3 ->{
        System.out.println("Quarter 1");     
        yield "Quarter 1";
      }
      case 4, 5, 6 -> {
        System.out.println("Quarter 2"); 
        yield "Quarter 2";
      }
          
      case 7, 8, 9 ->{
        System.out.println("Quarter 3");     
        yield "Quarter 3";
      }
               
      case 10, 11, 12 -> {
        System.out.println("Quarter 4");  
        yield "Quarter 4";            
      }
              
      default -> {
        System.out.println("Invalid month value passed");
        yield "Invalid month value";
      }
    };
    System.out.println("Value- " + value);
  }
}

That's all for this topic Switch Expressions in Java 12. If you have any doubt or any suggestions to make please drop a comment.Thanks!

>>>Return to Java Basics Tutorial Page


Related Topics

  1. Java Sealed Classes and Interfaces
  2. break Statement in Java With Examples
  3. Java Record Class With Examples
  4. Ternary Operator in Java
  5. Core Java Basics Interview Questions And Answers

You may also like-

  1. Private Methods in Java Interface
  2. Multi-Catch Statement in Java Exception Handling
  3. Creating a Thread in Java
  4. How HashMap Works Internally in Java
  5. Byte Streams in Java IO
  6. Check if Given String or Number is a Palindrome Java Program
  7. Spring Boot Hello World Web Application Example
  8. Connection Pooling With Apache DBCP Spring Example

Tuesday, April 7, 2026

return Statement in Java With Examples

In previous tutorials we have already seen continue statement which is used to continue the iteration of a loop and break statement which is used to break out of a loop, in this post, we’ll explore the return statement in Java, a fundamental control flow element which is used to explicitly return from a method.

What is the Return Statement in Java

When a return statement is encountered in a method that method’s execution immediately terminates and control transfers back to the calling method. Depending on the method’s signature, a return statement may return a value or simply exit without returning anything.

Rules for using Java return statement

  1. Void methods and return
    • If a method does not return a value, its signature must declare void.
    • Example: void methodA() { ... }
    • In such methods, a return; statement is optional and can be used to exit early under certain conditions.
  2. Methods returning a value
    • If a method returns a value, its signature must specify the return type.
    • Example: int methodB() { return 5; }
    • The type of the returned value must be compatible with the declared return type.
  3. Type compatibility
    • The return type of the method and actual value returned should be compatible.
    • The compiler enforces strict type checking. For instance, a method declared to return double cannot return a String.

Java return keyword example

1- A method returning int value.

public class ReturnExample {

 public static void main(String[] args) {
  ReturnExample obj = new ReturnExample();
  int sum = obj.add(6,  7);
  System.out.println("Sum is- " + sum);
 }
 
 int add(int a, int b) {
  int sum = a + b;
  return sum;
 }
}

Output

Sum is- 13

2- A void method with return statement as a control statement to terminate method execution when the condition is satisfied.

public class ReturnExample {
  public static void main(String[] args) {
    ReturnExample obj = new ReturnExample();
    obj.display();
    System.out.println("After method call...");
  }
    
  void display() {
    for(int i = 1; i <= 10; i++) {
      // method execution terminates when this 
      //condition is true
      if (i > 5)
        return;
      System.out.println(i);
    }
  }
}

Output

1
2
3
4
5
After method call...

In the example, there is a for loop in method with a condition to return from the method. When the condition is true, method execution is terminated and the control returns to the calling method.

One point to note here is that in a method return statement should be the last statement or it should be with in a condition. Since method execution terminates as soon as return statement is encountered so having any statements after return results in “Unreachable code” error.

public class ReturnExample {

 public static void main(String[] args) {
  ReturnExample obj = new ReturnExample();
  obj.display();
  System.out.println("After method call...");
 }
 
 void display() {
  int i;
  return;
  i++; // error
 }
}

In the example there is code after the return statement which is never going to be executed thus the “Unreachable code” compile time error.

That's all for this topic return Statement in Java With Examples. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Java Basics Tutorial Page


Related Topics

  1. Java do-while Loop With Examples
  2. Conditional Operators in Java
  3. Why Class Name And File Name Should be Same in Java
  4. Object Creation Using new Operator in Java
  5. Literals in Java

You may also like-

  1. Method Overriding in Java
  2. Interface in Java With Examples
  3. Java String Search Using indexOf(), lastIndexOf() And contains() Methods
  4. try-catch Block in Java Exception Handling
  5. Split a String in Java Example Programs
  6. Java Program to Reverse a Number
  7. Serialization and Deserialization in Java
  8. Nested Class And Inner Class in Java

Structured Output In LangChain

When interacting with LLMs the response you get is a free-form text. In many scenarios you may need a structured response which follows a pre-defined format. Some of those scenarios are listed below-

  • API calling- You may need to pass the response of the model to API calls. REST APIs are built to consume structured data such as JSON or XML. If LLM returns structured output you can directly send it to API methods.
  • Storing to DB- If LLM response is compatible to the DB table structure where that data has to be stored then it is easy to directly save the LLM response into the DB table.
  • Agents- In a multi-agent workflow, communication between agent becomes more reliable and efficient by enforcing a structured output. With free-form text there is a greater chance of next agent in the workflow misinterpreting the response.

Structured output in LangChain

In LangChain, structured output is a process used to force LLMs to return their responses in a specific, pre-defined format (such as JSON or a Pydantic object) instead of free-form text. This ensures predictability and allows for direct integration with downstream applications and APIs.

Structured output in LangChain, frequently reduces token usage too by eliminating unnecessary conversational text, whitespace, and formatting tokens.

How to achieve formatted output

  • with_structured_output()- This is a helper method that lets you wrap a model call so its response is automatically parsed into a structured schema.
  • Output parsers- Using output parsers is another way to format the output. It is used for models that do not natively support structured output. With structured output, formatting is done by the model itself and you get the formatted response where as with output parsers, the parser performs the actual formatting by transforming that text into a structured programmatic object after the LLM returns its raw text response.

In this article we’ll stick to structured output in LangChain for formatting output.

Supported Schema Formats for Structured Output

1. TypedDict

A Pythonic way to define dictionary structures where we specify which key and value pairs should form the output. One drawback of using TypedDict is that it does not validate the data at runtime.

Structured Output with TypedDict example

Suppose you want to send a prompt to the model to list 3 books of the given author. You want response in a specific format with only 3 fields title, year first published and main characters in the book. That schema structure can be defined using TypedDict. Note that TypedDict in Python is always defined by creating a class that inherits from TypedDict.

Inference provider used in the example is Groq, so you need to define GROQ_API_KEY. Also ensure installation of langchain-groq package.

from typing import List, Optional, TypedDict
from langchain_groq import ChatGroq
from langchain_core.prompts import HumanMessagePromptTemplate, ChatPromptTemplate
from dotenv import load_dotenv

load_dotenv()

# Define a TypedDict for a single book
class BookModel(TypedDict):
    title: str
    year_first_published: int
    main_characters: Optional[list[str]]

# Define a TypedDict for multiple books (container)
class BooksResponse(TypedDict):
    books: List[BookModel]

prompt = ChatPromptTemplate.from_messages([
    {"role": "system", "content": "You are a helpful assistant that provides information about books."},
    HumanMessagePromptTemplate.from_template("List 3 books of author {author}.")
])

model = ChatGroq(model="qwen/qwen3-32b", temperature=0.2)

# Wrap with structured output using TypedDict
structured_model = model.with_structured_output(BooksResponse)

chain = prompt | structured_model

response = chain.invoke({"author": "Agatha Christie"})

print(response)

Output

{'books': [{'main_characters': ['Hercule Poirot', 'Detective Inspector Japp', 'Countess Andrenyi'], 
'title': 'Murder on the Orient Express', 'year_first_published': 1934}, 
{'main_characters': ['Hercule Poirot', 'Dr. Sheppard', 'Roger Ackroyd'], 
'title': 'The Murder of Roger Ackroyd', 'year_first_published': 1926}, 
{'main_characters': ['Justice Wargrave', ' Vera Claythorne', 'Philip Lombard'], 
'title': 'And Then There Were None', 'year_first_published': 1939}]}

Points to note here-

  • Since list of books is needed as response so two separate TypedDict classes are defined, one of them acts as a container of books.
  • Main characters field is kept optional. That makes it a non-mandatory field in response.
  • You have to wrap model in with_structured_output() method, passing the output schema as a TypedDict class in this case.
    structured_model = model.with_structured_output(BooksResponse)
    

Pydantic Models

Pydantic model provides robust data validation which is missing in TypedDict. With Pydantic you can define a schema structure which guarantees type safety. When you instantiate a Pydantic model, it checks that the provided values match the declared types. If they don’t, it raises a ValidationError.

Structured output with Pydantic example

If we take the same example of listing 3 books of the given author. You want response in specific format with only 3 fields title, year first published and main characters in the book. That schema structure can be defined using Pydantic.

Gemini model is used in the example, so you need to define GEMINI_API_KEY. Also ensure installation of langchain_google_genai package.

  from pydantic import BaseModel
  from typing import Optional, List
  from langchain_google_genai import ChatGoogleGenerativeAI
  from langchain_core.prompts import HumanMessagePromptTemplate, ChatPromptTemplate
  from dotenv import load_dotenv

  load_dotenv()

  # Define a Pydantic model for a single book
  class BookModel(BaseModel):
      title: str
      year_first_published: int
      main_characters: Optional[list[str]]

  # Define a Pydantic model for multiple books (container)
  class BooksResponse(BaseModel):
      books: List[BookModel]

  prompt = ChatPromptTemplate.from_messages([
      {"role": "system", "content": "You are a helpful assistant that provides information about books."},
      HumanMessagePromptTemplate.from_template("List 3 books of author {author}.")
  ])

  model = ChatGoogleGenerativeAI(model="gemini-3.1-flash-lite-preview", temperature=0.2)

  # Wrap with structured output using Pydantic
  structured_model = model.with_structured_output(BooksResponse)

  chain = prompt | structured_model

  response = chain.invoke({"author": "Agatha Christie"})
  print(response)

Output

books=[BookModel(title='The Murder of Roger Ackroyd', year_first_published=1926, 
main_characters=['Hercule Poirot', 'James Sheppard']), 
BookModel(title='Murder on the Orient Express', year_first_published=1934, 
main_characters=['Hercule Poirot']), 
BookModel(title='And Then There Were None', year_first_published=1939, 
main_characters=['Vera Claythorne', 'Philip Lombard', 'Lawrence Wargrave'])]

{'books': [{'title': 'The Murder of Roger Ackroyd', 'year_first_published': 1926, 
'main_characters': ['Hercule Poirot', 'James Sheppard']}, 
{'title': 'Murder on the Orient Express', 'year_first_published': 1934, 
'main_characters': ['Hercule Poirot']}, 
{'title': 'And Then There Were None', 'year_first_published': 1939, 
'main_characters': ['Vera Claythorne', 'Philip Lombard', 'Lawrence Wargrave']}]}

JSON Schema

You can also define structured output using JSON Schema which is a standard, language-agnostic format useful for cross-system interoperability. If you have API calls which are written in different programming languages then JSON Schema is a better option. It is a universal standard, any system (Python, Java, Node.js, etc.) can validate against it, making it ideal for cross platform workflows.

Structured output with JSONSchema example

  from langchain_google_genai import ChatGoogleGenerativeAI
  from langchain_core.prompts import HumanMessagePromptTemplate, ChatPromptTemplate
  from dotenv import load_dotenv

  load_dotenv()

  # Define a JSON Schema for a single book
  book_schema = {
      "title": "BookModel",
      "type": "object",
      "properties": {
          "title": {"type": "string"},
          "year_first_published": {"type": "integer"},
          "main_characters": {
              "type": "array",
              "items": {"type": "string"},
              "maxItems": 3   # restrict to 3 characters
          }
      },
      "required": ["title", "year_first_published"]
  }

  # Define a JSON Schema for multiple books (container)
  books_response_schema = {
      "title": "BooksResponse",
      "type": "object",
      "properties": {
          "books": {
              "type": "array",
              "items": book_schema,
              "minItems": 3,
              "maxItems": 3
          }
      },
      "required": ["books"]
  }

  prompt = ChatPromptTemplate.from_messages([
      {"role": "system", "content": "You are a helpful assistant that provides information about books."},
      HumanMessagePromptTemplate.from_template("List 3 books of author {author}.")
  ])

  model = ChatGoogleGenerativeAI(model="gemini-3.1-flash-lite-preview", temperature=0.2)

  # Wrap with structured output using JSON Schema
  structured_model = model.with_structured_output(books_response_schema)

  chain = prompt | structured_model

  response = chain.invoke({"author": "Agatha Christie"})

  print(response)

Output

{'books': [{'title': 'The Murder of Roger Ackroyd', 'year_first_published': 1926, 
'main_characters': ['Hercule Poirot', 'James Sheppard']}, 
{'title': 'Murder on the Orient Express', 'year_first_published': 1934, 
'main_characters': ['Hercule Poirot']}, 
{'title': 'And Then There Were None', 'year_first_published': 1939, 
'main_characters': ['Vera Claythorne', 'Philip Lombard', 'Lawrence Wargrave']}]}

Points to note here-

  • book_schema defines the structure for one book.
  • books_response_schema wraps a list of exactly 3 books.
  • Main characters is restricted to max 3 items using maxItems: 3.
  • With JSON Schema you can enforce rules like maxItems, minItems, pattern, enum, or numeric ranges directly in the schema.

That's all for this topic Structured Output In LangChain. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. What is LangChain - An Introduction
  2. Messages in LangChain
  3. Prompt Templates in LangChain With Examples
  4. LangChain PromptTemplate + Streamlit - Code Generator Example
  5. Chatbot With Chat History - LangChain MessagesPlaceHolder

You may also like-

  1. return Statement in Java With Examples
  2. Array in Java
  3. Count Number of Words in a String Java Program
  4. Ternary Operator in Java With Examples
  5. Java Multithreading Interview Questions And Answers
  6. Java Exception Handling Tutorial
  7. ConcurrentHashMap in Java With Examples
  8. TreeMap in Java With Examples

Chatbot With Chat History - LangChain MessagesPlaceHolder

In the post Chain Using LangChain Expression Language With Examples we created a simple chatbot using Streamlit UI. As pointed out there itself Large Language Models (LLMs) are stateless. By design, they do not retain memory of past interactions, treating each API call as a fresh, independent request. In this tutorial we’ll see how to create a chatbot that stores the chat history using MessagesPlaceholder and RunnableWithMessageHistory in LangChain. Sometimes it is required to keep chat history in order to provide contextual information to the model.

MessagesPlaceholder in LangChain

MessagesPlaceholder in LangChain is a prompt component which is used to inject a dynamic list of messages, such as chat history, directly into a ChatPromptTemplate.

  • When user interacts with a model, the messages sent by user are classified as human messages whereas the replies from the model are classified as AI messages.
  • You can store the previous human/AI messages and inject it as previous chat history, when sending a query to model, using MessagesPlaceholder.

That way you can provide context to the model. You need to pass a key with MessagesPlaceholder by which it can identify which variable to use as messages.

Here is a simple example where some of the messages are manually setup as human and AI messages.

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "You are a helpful assistant."),
        MessagesPlaceholder("history"),
        ("human", "{question}"),
    ]
)
prompt.invoke(
    {
        "history": [("human", "what's 5 + 2"), ("ai", "5 + 2 is 7")],
        "question": "now multiply that by 4",
    }
)

As you can see, MessagesPlaceholder is passed as one of the prompt components. The key passed to it is "history" which is same as the key "history" used in the dictionary passed with prompt.invoke().

So essentially what is the sent to the model is as given below-

ChatPromptValue(messages=[
     SystemMessage(content="You are a helpful assistant."),
     HumanMessage(content="what's 5 + 2"),
     AIMessage(content="5 + 2 is 7"),
    HumanMessage(content="now multiply that by 4"),
])

The latest human message is "now multiply that by 4", but previous messages are also sent to the model to retain the context.

RunnableWithMessageHistory in LangChain

RunnableWithMessageHistory class in LangChain is used to manage chat message history for another Runnable. The highlight of using RunnableWithMessageHistory is the session support. It uses a session_id to look up or create a specific chat message history, allowing it to handle multiple concurrent users or conversations.

Configuring RunnableWithMessageHistory

To use RunnableWithMessageHistory, you must provide the following:

  • get_session_history: A factory function that takes a single positional argument session_id of type string and returns the chat history instance associated with the passed session_id.
  • input_messages_key: It specifies which key contains the current user message. Required if the wrapped chain accepts a dictionary as input;
  • history_messages_key: Specifies the key where historical messages should be inserted in the prompt template.
  • output_messages_key: it identifies the key containing the model's response, required if the wrapped chain returns a dictionary.

For example-

with_message_history = RunnableWithMessageHistory(
        chatbot_chain,
        get_session_history,
        input_messages_key="user_input",
        history_messages_key="chat_history",
    )
How to use RunnableWithMessageHistory

When invoking a wrapped chain, you must pass the session ID in the configuration:

with_message_history.invoke(
   {"user_input": user_input},
    config = {"configurable": {"session_id": "user_123"}}
)

Using chat_message_histories module

chat_message_histories module in LangChain provides standardized way to store history of the message interactions in a chat. This module provides many classes to seamlessly integrate with, file system (FileChatMessageHistory), various databases (Cassandra, Postgres, Redis), In-Memory Storage (ChatMessageHistory).

Chatbot with chat history using LangChain and StreamLit

Using the above mentioned classes of LangChain, it becomes very easy to store conversational history.

In the nutshell role of the classes is as given below-

MessagesPlaceholder- To inject the chat history so that model has the contextual information.

RunnableWithMessageHistory- To manage chat history and provide session support. That way you can have many instances of the chatbot maintaining their own sessions.

ChatMessageHistory- Which acts as a in-memory storage of the chat history. Good for initial demo but for production use a DB backed chatbot.

Chatbot first asks for user’s ID, that is done to use the passed userID as the sessionID.

Chatbot using MessagesPlaceHolder

Streamlit UI related code is kept in a separate file.

app.py

import streamlit as st
from chatbot import generate_response

# Streamlit app to demonstrate the simple chain
st.set_page_config(page_title="Chatbot", layout="centered")
st.title(🤖 Chatbot With Context")
# Initialize session state
if "user_id" not in st.session_state:
    st.session_state.user_id = None
if "chat_history" not in st.session_state:
    st.session_state.chat_history = []

#Ensure user_id is set before allowing chat interactions
if st.session_state.user_id is None:
    user_id_input = st.text_input("Enter your User ID:")
    if st.button("Confirm User ID"):
        if user_id_input.strip() == "":
            st.warning("User ID is required to continue.")
        else:
            st.session_state.user_id = user_id_input.strip()
            st.success(f"User ID set: {st.session_state.user_id}")

if st.session_state.user_id:
    for message in st.session_state.chat_history:
        with st.chat_message(message["role"]):
            st.markdown(message["content"])

    user_input = st.chat_input("Enter your query:")  

    if user_input:
        st.session_state.chat_history.append( {"role": "user", "content": user_input})
        with st.chat_message("user"):
            st.markdown(user_input)
        response = generate_response(user_input, st.session_state.user_id)
        st.session_state.chat_history.append({"role": "assistant", "content": response})
        with st.chat_message("assistant"):
            st.markdown(f"**Chatbot Response:** {response}")   
    else:
        st.warning("Please enter a query to get a response.")

chatbot.py

from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate
from langchain_core.prompts import MessagesPlaceholder
from langchain_ollama import ChatOllama

from langchain_core.messages import SystemMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

# Define system and human message templates
system_message = SystemMessage(content="You are a helpful assistant that responds to user queries.")
# Define the output parser
parser = StrOutputParser()

# In-Memory Store to hold chat histories for different sessions 
# (not suitable for production, just for demo purposes)
store = {}

# Function to retrieve chat history for a given session_id, or create a new one if it doesn't exist
def get_session_history(session_id: str):
    print(f"Retrieving chat history for session_id: {session_id}")
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

def generate_response(user_input: str, session_id: str) -> str:
    session_id = session_id or "default_session"
    # Create a ChatPromptTemplate object with MessagePlaceholder for conversation history
    prompt = ChatPromptTemplate.from_messages([
        system_message, 
        MessagesPlaceholder(variable_name="chat_history"),
        HumanMessagePromptTemplate.from_template("{user_input}")
    ]) 

    # Initialize the model
    model = ChatOllama(model="llama3.1", temperature=0.1)            

    # Chain the prompt, model, and parser together using RunnableWithMessageHistory
    chatbot_chain = prompt | model | parser

    with_message_history = RunnableWithMessageHistory(
        chatbot_chain,
        get_session_history,
        input_messages_key="user_input",
        history_messages_key="chat_history",
    )
    response = with_message_history.invoke(
        {"user_input": user_input},
        config={"configurable": {"session_id": session_id}}
    )
    
    return response
Chatbot With Chat History

Drawbacks of using MessagesPlaceholder, RunnableWithMessageHistory, ChatMessageHistory

Using MessagesPlaceholder and ChatMessageHistory along with RunnableWithMessageHistory in LangChain provides powerful capability to store chat history, but they come with notable drawbacks regarding complexity, performance, and maintainability.

  1. Complexity in Debugging: Because the content is inserted dynamically in MessagesPlaceholder, it can be difficult to visualize the final prompt sent to the LLM, making debugging tricky.
  2. Context Management Overhead: MessagesPlaceholder only acts as a placeholder. It does not automatically manage the history, meaning the developer is responsible for passing the correct history list every time, increasing code complexity.
  3. Context Window Limits (Token Exhaustion): Without any trimming, chat history grows indefinitely. This leads to exceeding the LLL's token limit (context window) and increasing API costs.
  4. In-Memory Persistence: By default, in-memory history is lost when the application restarts. Storing conversations in memory can become a performance bottleneck for large-scale applications.

That's all for this topic Chatbot With Chat History - LangChain MessagesPlaceHolder. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. LangChain PromptTemplate + Streamlit - Code Generator Example
  2. Prompt Templates in LangChain With Examples
  3. RunablePassthrough in LangChain With Examples
  4. RunableSequence in LangChain With Examples
  5. RunnableBranch in LangChain With Examples

You may also like-

  1. How ArrayList Works Internally in Java
  2. How HashSet Works Internally in Java
  3. Why wait(), notify() And notifyAll() Must be Called Inside a Synchronized Method or Block
  4. Synchronization in Java - Synchronized Method And Block
  5. Best Practices For Exception Handling in Java
  6. Java Abstract Class and Abstract Method
  7. Just In Time Compiler (JIT) in Java
  8. Circular Dependency in Spring Framework

Monday, April 6, 2026

removeIf() Method in Java Collection With Examples

In this post, we’ll explore how the removeIf() Method in Java can be used to efficiently remove elements from a Collection based on a given condition. Introduced in Java 8 as part of the java.util.Collection interface, the removeIf() method leverages functional programming by accepting a Predicate- a powerful way to define conditions in a clean, declarative style.

Since removeIf() is defined in the Collection interface, you can use it with Collections like ArrayList, HashSet that implements Collection interface. To use it with HashMap you will have to get a collection view of the Map, since Map doesn't implement Collection interface.


Syntax of removeIf() Method in Java

boolean removeIf(Predicate<? super E> filter)

Parameter: A Predicate functional interface that evaluates each element and returns true if the element should be removed.

Return Value: Returns true if any elements were removed, otherwise false.

Removing elements from ArrayList using removeIf() method

In this example we'll have a list of cities and we'll remove elements from this ArrayList using removeIf() method. In passed Predicate if the condition holds true for any of the elements, then that element is removed from the list.

import java.util.ArrayList;
import java.util.List;

public class RemoveIf {

  public static void main(String[] args) {
    List<String> cityList = new ArrayList<String>();
    cityList.add("Delhi");
    cityList.add("Mumbai");
    cityList.add("Kolkata");
    cityList.add("Hyderabad");
    cityList.add("Bangalore");
    cityList.add("Mumbai");
    System.out.println("*** List Initially ***");
    System.out.println(cityList);
    cityList.removeIf(p -> p.equalsIgnoreCase("Hyderabad") || 
          p.equalsIgnoreCase("Bangalore"));
    System.out.println("After Removal " + cityList);
  }
}

Output

*** List Initially ***
[Delhi, Mumbai, Kolkata, Hyderabad, Bangalore, Mumbai]
After Removal [Delhi, Mumbai, Kolkata, Mumbai]

Removing elements from HashSet using removeIf() method

You can use removeIf() method with HashSet also to remove elements from the Set based on the passed condition. In the given example condition is to remove cities having name of length more than 6.

import java.util.HashSet;
import java.util.Set;

public class RemoveIf {

  public static void main(String[] args) {
    // creating a HashSet
    Set<String> citySet = new HashSet<String>();
    // Adding elements
    citySet.add("London");        
    citySet.add("Tokyo");
    citySet.add("New Delhi");
    citySet.add("Beijing");
    citySet.add("Nairobi");
    System.out.println("*** Set Initially ***");
    System.out.println(citySet);
    
    // Remove all cities having length more than 6
    citySet.removeIf(e -> e.length() > 6);
    System.out.println("After Removal " + citySet);
  }
}

Output

*** Set Initially ***
[Beijing, New Delhi, Nairobi, Tokyo, London]
After Removal [Tokyo, London]

Removing elements from HashMap using removeIf() method

To use removeIf() method with a Map you have to get Collection view (like entrySet(), keySet(), or values()) of a Map. After getting the Collection view of a Map removeIf() method can be used.

import java.util.HashMap;
import java.util.Map;


public class RemoveIf {

  public static void main(String[] args) {
    Map<String, String> cityMap = new HashMap<String, String>();
    // Adding elements
    cityMap.put("1","New York City" );
    cityMap.put("2", "New Delhi");
    cityMap.put("3", "Mumbai");
    cityMap.put("4", "Beijing");
    cityMap.put("5", "Berlin");

    System.out.println("*** Map Initially ***");
    System.out.println(cityMap);
      
    // Use entrySet to get Set view of all Map entries. 
    // Remove entry from Map based on the condition for value.
    cityMap.entrySet().removeIf(entry -> entry.getValue().equals("Beijing"));
    System.out.println("*** Map After removal ***");
    System.out.println(cityMap);
  }
}

Output

*** Map Initially ***
{1=New York City, 2=New Delhi, 3=Mumbai, 4=Beijing, 5=Berlin}
*** Map After removal ***
{1=New York City, 2=New Delhi, 3=Mumbai, 5=Berlin}

That's all for this topic removeIf() Method in Java Collection With Examples. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. How to Remove Entry From HashMap in Java
  2. How to Remove Elements From an ArrayList in Java
  3. How to Remove Duplicate Elements From an ArrayList in Java
  4. Java Map replace() With Examples
  5. HashSet Vs LinkedHashSet Vs TreeSet in Java

You may also like-

  1. Difference Between Comparable and Comparator in Java
  2. CopyOnWriteArraySet in Java With Examples
  3. Switch Expressions in Java 12
  4. Java Nested Class And Inner Class
  5. Reading File in Java Using Files.lines And Files.newBufferedReader
  6. Angular Cross Component Communication Using Subject Observable
  7. Angular HttpClient - Set Response Type as Text
  8. Spring Web MVC Tutorial

Sunday, April 5, 2026

Java Lambda Expression as Method Parameter

In this guide, we’ll explore how to use a Java Lambda Expression as Method Parameter. Lambda expressions provide a concise way to implement the abstract method of a functional interface, making code cleaner and more expressive. Since the target type of a lambda is always a functional interface, you can pass a lambda wherever such an interface is expected, including as a method argument. To pass a lambda expression as a method parameter in Java, the type of the method argument, which receives the lambda expression as a parameter, must be of functional interface type.

Why Pass Lambda Expressions as Parameters

Using a Java Lambda Expression as Method Parameter allows developers to write flexible and reusable methods. Instead of hard‑coding behavior, you can pass custom logic directly into a method, reducing boilerplate and improving readability. This is especially useful in APIs like Java’s Collections framework and Stream API, where lambdas are frequently used for filtering, mapping, and sorting.