Tutorial

Restful Web Services Tutorial in Java

Published on August 3, 2022
author

Pankaj

Restful Web Services Tutorial in Java

Welcome to Restful Web Services Tutorial in Java. REST is the acronym for REpresentational State Transfer. REST is an architectural style for developing applications that can be accessed over the network. REST architectural style was brought in light by Roy Fielding in his doctoral thesis in 2000.

Restful Web Services

Restful Web Services is a stateless client-server architecture where web services are resources and can be identified by their URIs. REST Client applications can use HTTP GET/POST methods to invoke Restful web services. REST doesn’t specify any specific protocol to use, but in almost all cases it’s used over HTTP/HTTPS. When compared to SOAP web services, these are lightweight and doesn’t follow any standard. We can use XML, JSON, text or any other type of data for request and response.

Java RESTful Web Services API

Java API for RESTful Web Services (JAX-RS) is the Java API for creating REST web services. JAX-RS uses annotations to simplify the development and deployment of web services. JAX-RS is part of JDK, so you don’t need to include anything to use it’s annotations.

Restful Web Services Annotations

Some of the important JAX-RS annotations are:

  • @Path: used to specify the relative path of class and methods. We can get the URI of a webservice by scanning the Path annotation value.
  • @GET, @PUT, @POST, @DELETE and @HEAD: used to specify the HTTP request type for a method.
  • @Produces, @Consumes: used to specify the request and response types.
  • @PathParam: used to bind the method parameter to path value by parsing it.

Restful Web Services and SOAP

  1. SOAP is a protocol whereas REST is an architectural style.
  2. SOAP server and client applications are tightly coupled and bind with the WSDL contract whereas there is no contract in REST web services and client.
  3. Learning curve is easy for REST when compared to SOAP web services.
  4. REST web services request and response types can be XML, JSON, text etc. whereas SOAP works with XML only.
  5. JAX-RS is the Java API for REST web services whereas JAX-WS is the Java API for SOAP web services.

REST API Implementations

There are two major implementations of JAX-RS API.

  1. Jersey: Jersey is the reference implementation provided by Sun. For using Jersey as our JAX-RS implementation, all we need to configure its servlet in web.xml and add required dependencies. Note that JAX-RS API is part of JDK not Jersey, so we have to add its dependency jars in our application.
  2. RESTEasy: RESTEasy is the JBoss project that provides JAX-RS implementation.

Java Restful Web Services Tutorial

Let’s see how easy to create Restful web service using Jersey and then RESTEasy. We will be exposing following methods over HTTP and use Chrome Postman extension to test these.

URI HTTP Method Description
/person/{id}/getDummy GET Returns a dummy person object
/person/add POST Adds a person
/person/{id}/delete GET Delete the person with ‘id’ in the URI
/person/getAll GET Get all persons
/person/{id}/get GET Get the person with ‘id’ in the URI

Jersey Restful Web Services

Create a dynamic web project and then convert it to Maven to get the skeleton of your web services project. Below image shows the project structure of the final project. JAXRS Jersey Project Let’s look at the Jersey dependencies we have in pom.xml file.

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>JAXRS-Example</groupId>
  <artifactId>JAXRS-Example</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  
  <dependencies>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-server</artifactId>
            <version>1.19</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-servlet</artifactId>
            <version>1.19</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-client</artifactId>
            <version>1.19</version>
        </dependency>
  </dependencies>
  
  <build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
      <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.6</version>
        <configuration>
          <warSourceDirectory>WebContent</warSourceDirectory>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.3</version>
        <configuration>
          <source>1.7</source>
          <target>1.7</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

We are not required to add jersey-client dependencies but if you are writing java program to invoke a REST web service using Jersey then it’s required. Now let’s look at the deployment descriptor to learn how to configure Jersey to create our web application.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="https://xmlns.jcp.org/xml/ns/javaee https://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>JAXRS-Example</display-name>

<!-- Jersey Servlet configurations -->
    <servlet>
    <servlet-name>Jersey REST Service</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>com.sun.jersey.config.property.packages</param-name>
      <param-value>com.journaldev</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Jersey REST Service</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
  <!-- Jersey Servlet configurations -->

</web-app>

That’s all is required to plugin Jersey into our web application, in our java code we will be using JAX-RS annotations. Notice the value of init parameter com.sun.jersey.config.property.packages to provide package that will be scanned for web service resources and methods.

REST Example Model Classes

First of all we will create two model beans - Person for our application data and Response for sending response to client systems. Since we will be sending XML response, the beans should be annotated with @XmlRootElement, hence we have this class.

package com.journaldev.jaxrs.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement (name="person")
public class Person {
	private String name;
	private int age;
	private int id;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}
	
	@Override
	public String toString(){
		return id+"::"+name+"::"+age;
	}

}
package com.journaldev.jaxrs.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Response {

	private boolean status;
	private String message;

	public boolean isStatus() {
		return status;
	}

	public void setStatus(boolean status) {
		this.status = status;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
}

REST Web Services Tutorial Services

Based on our URI structure, below is the service interface and it’s implementation code.

package com.journaldev.jaxrs.service;

import com.journaldev.jaxrs.model.Person;
import com.journaldev.jaxrs.model.Response;

public interface PersonService {

	public Response addPerson(Person p);
	
	public Response deletePerson(int id);
	
	public Person getPerson(int id);
	
	public Person[] getAllPersons();

}
package com.journaldev.jaxrs.service;


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

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.journaldev.jaxrs.model.Person;
import com.journaldev.jaxrs.model.Response;

@Path("/person")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public class PersonServiceImpl implements PersonService {

	private static Map<Integer,Person> persons = new HashMap<Integer,Person>();
	
	@Override
	@POST
    @Path("/add")
	public Response addPerson(Person p) {
		Response response = new Response();
		if(persons.get(p.getId()) != null){
			response.setStatus(false);
			response.setMessage("Person Already Exists");
			return response;
		}
		persons.put(p.getId(), p);
		response.setStatus(true);
		response.setMessage("Person created successfully");
		return response;
	}

	@Override
	@GET
    @Path("/{id}/delete")
	public Response deletePerson(@PathParam("id") int id) {
		Response response = new Response();
		if(persons.get(id) == null){
			response.setStatus(false);
			response.setMessage("Person Doesn't Exists");
			return response;
		}
		persons.remove(id);
		response.setStatus(true);
		response.setMessage("Person deleted successfully");
		return response;
	}

	@Override
	@GET
	@Path("/{id}/get")
	public Person getPerson(@PathParam("id") int id) {
		return persons.get(id);
	}
	
	@GET
	@Path("/{id}/getDummy")
	public Person getDummyPerson(@PathParam("id") int id) {
		Person p = new Person();
		p.setAge(99);
		p.setName("Dummy");
		p.setId(id);
		return p;
	}

	@Override
	@GET
	@Path("/getAll")
	public Person[] getAllPersons() {
		Set<Integer> ids = persons.keySet();
		Person[] p = new Person[ids.size()];
		int i=0;
		for(Integer id : ids){
			p[i] = persons.get(id);
			i++;
		}
		return p;
	}

}

Most of the code is self explanatory, spend some time to familiarize yourself with JAX-RS annotations @Path, @PathParam, @POST, @GET, @Consumes and @Produces.

Restful Web Services Test

That’s it. Our web service is ready, just export it as WAR file and put it inside Tomcat webapps directory or deploy into any other container of your choice. Below are some of the tests performed using Postman chrome extension for this web service. Note that we have to provide Accept and Content-Type values as “application/xml” in request header as shown in below image. JAXRS Headers Accept

  • getDummy Restful web services java
  • add Java Restful web services tutorial
  • get Rest Web Services tutorial
  • getAll Jersey rest web services
  • delete Jersey REST API

That’s all for creating web services using Jersey JAX-RS implementation. As you can see that most of the code is using JAX-RS annotations and Jersey is plugged in through deployment descriptor and dependencies.

RESTEasy RESTful Web Services Example

We will use all the business logic developed in Jersey project, but rather than making changes to the same project, I have created a new project. Create a dynamic web project and convert it to Maven project. Then copy all the java classes - Person, Response, PersonService and PersonServiceImpl. Below is the final project after we are done with all the changes. JAXRS RestEasy Project Add below RESTEasy dependencies in pom.xml file.

<dependency>
	<groupId>org.jboss.resteasy</groupId>
	<artifactId>resteasy-jaxrs</artifactId>
	<version>3.0.13.Final</version>
</dependency>
<dependency>
	<groupId>org.jboss.resteasy</groupId>
	<artifactId>resteasy-jaxb-provider</artifactId>
	<version>3.0.13.Final</version>
</dependency>

Below is the web.xml file where we are configuring Resteasy servlet.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="https://xmlns.jcp.org/xml/ns/javaee https://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>JAXRS-Example-RestEasy</display-name>
     
    <listener>
      <listener-class>
         org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
      </listener-class>
   	</listener>
   
    <servlet>
        <servlet-name>resteasy-servlet</servlet-name>
        <servlet-class>
            org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
        </servlet-class>
        <init-param>
        <param-name>javax.ws.rs.Application</param-name>
        <param-value>com.journaldev.jaxrs.resteasy.app.MyApp</param-value>
    </init-param>
    </servlet>
  
    <servlet-mapping>
        <servlet-name>resteasy-servlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
	
</web-app>

Notice the init-param where are providing MyApp class as value, here we are extending javax.ws.rs.core.Application class as shown below.

package com.journaldev.jaxrs.resteasy.app;

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

import javax.ws.rs.core.Application;

import com.journaldev.jaxrs.service.PersonServiceImpl;

public class MyApp extends Application {
	
	private Set<Object> singletons = new HashSet<Object>();

	public MyApp() {
		singletons.add(new PersonServiceImpl());
	}

	@Override
	public Set<Object> getSingletons() {
		return singletons;
	}

}

RESTEasy Web Services Test

That’s it. Our web service is ready with RESTEasy JAX-RS implementation. Below are some of the output from Postman chrome extension test.

  • getDummy rest services in java, rest client java, java rest api, REST Tutorial
  • add Restful web services java, Java Restful web services
  • get Restful Web Services

That’s all for Restful Web Services Tutorial, I hope you learned about JAX-RS annotations and understood the benefits of having standard API that helped us in reusing code and moving from Jersey to RESTEasy so easy.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Pankaj

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
JournalDev
DigitalOcean Employee
DigitalOcean Employee badge
February 14, 2021

Hello Pankaj, I tried to test your code unfortunately I received the error 500 as below. i am using netbeans somewhere i read that the date i jar was not loaded can you tell me what to do thanks in advance HTTP Status 500 - Internal Server Error type Exception report messageInternal Server Error descriptionThe server encountered an internal error that prevented it from fulfilling this request. exception java.lang.AbstractMethodError: javax.ws.rs.core.Response$ResponseBuilder.status(ILjava/lang/String;)Ljavax/ws/rs/core/Response$ResponseBuilder; note The full stack traces of the exception and its root causes are available in the GlassFish Server Open Source Edition 5.1.0 logs. GlassFish Server Open Source Edition 5.1.0

- Heni

    JournalDev
    DigitalOcean Employee
    DigitalOcean Employee badge
    November 6, 2020

    Hello Pankaj Sir…Please tell me how to implement rest web services in netbeans and test in postmen tool?

    - SUSHMITA MHATRE

      JournalDev
      DigitalOcean Employee
      DigitalOcean Employee badge
      October 7, 2020

      We need add: javax.xml.bind jaxb-api 2.3.1 No? If no, @XmlRootElement has error. I ask.

      - Javier Benito

        JournalDev
        DigitalOcean Employee
        DigitalOcean Employee badge
        March 28, 2020

        HI Pankaj, I am getting 400-Bad Request Error with the same request that you have made. Please help!

        - harshwardhan sharma

          JournalDev
          DigitalOcean Employee
          DigitalOcean Employee badge
          January 13, 2020

          How to run this project in eclipse?

          - XAA

            JournalDev
            DigitalOcean Employee
            DigitalOcean Employee badge
            January 1, 2020

            RESTFUL with XML as response?

            - dannegm

              JournalDev
              DigitalOcean Employee
              DigitalOcean Employee badge
              October 29, 2019

              Excellent example, much appreciated.

              - Dallas Jones

                JournalDev
                DigitalOcean Employee
                DigitalOcean Employee badge
                October 18, 2019

                Great article. One correction. In Response class, we need to add the following to avoid error. @Produces (MediaType.APPLICATION_XML) @XmlRootElement (name = “response”)

                - Venky

                  JournalDev
                  DigitalOcean Employee
                  DigitalOcean Employee badge
                  July 23, 2019

                  I copied your files as it is, however I am getting below error when I am trying to start server. I am using Tomcat 8.5 and Java 11. . . . INFO: Scanning for root resource and provider classes in the packages: com.journaldev Jul 23, 2019 3:39:02 PM org.apache.catalina.core.ApplicationContext log SEVERE: StandardWrapper.Throwable java.lang.IllegalArgumentException at jersey.repackaged.org.objectweb.asm.ClassReader.(ClassReader.java:170) at jersey.repackaged.org.objectweb.asm.ClassReader.(ClassReader.java:153) at jersey.repackaged.org.objectweb.asm.ClassReader.(ClassReader.java:424) at com.sun.jersey.spi.scanning.AnnotationScannerListener.onProcess(AnnotationScannerListener.java:138) at com.sun.jersey.core.spi.scanning.uri.FileSchemeScanner$1.f(FileSchemeScanner.java:86) at com.sun.jersey.core.util.Closing.f(Closing.java:71) at com.sun.jersey.core.spi.scanning.uri.FileSchemeScanner.scanDirectory(FileSchemeScanner.java:83) at com.sun.jersey.core.spi.scanning.uri.FileSchemeScanner.scanDirectory(FileSchemeScanner.java:80) at com.sun.jersey.core.spi.scanning.uri.FileSchemeScanner.scanDirectory(FileSchemeScanner.java:80) at com.sun.jersey.core.spi.scanning.uri.FileSchemeScanner.scan(FileSchemeScanner.java:71) at com.sun.jersey.core.spi.scanning.PackageNamesScanner.scan(PackageNamesScanner.java:226) at com.sun.jersey.core.spi.scanning.PackageNamesScanner.scan(PackageNamesScanner.java:142) at com.sun.jersey.api.core.ScanningResourceConfig.init(ScanningResourceConfig.java:80) at com.sun.jersey.api.core.PackagesResourceConfig.init(PackagesResourceConfig.java:104) at com.sun.jersey.api.core.PackagesResourceConfig.(PackagesResourceConfig.java:78) at com.sun.jersey.api.core.PackagesResourceConfig.(PackagesResourceConfig.java:89) at com.sun.jersey.spi.container.servlet.WebComponent.createResourceConfig(WebComponent.java:696) at com.sun.jersey.spi.container.servlet.WebComponent.createResourceConfig(WebComponent.java:674) at com.sun.jersey.spi.container.servlet.WebComponent.init(WebComponent.java:205) at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:394) at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:577) at javax.servlet.GenericServlet.init(GenericServlet.java:158) at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1132) at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1079) at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:973) at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4885) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5199) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1412) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1402) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:834) Jul 23, 2019 3:39:02 PM org.apache.catalina.core.StandardContext loadOnStartup SEVERE: Servlet [Jersey REST Service] in web application [/JournaldevWebservice] threw load() exception java.lang.IllegalArgumentException at jersey.repackaged.org.objectweb.asm.ClassReader.(ClassReader.java:170) at jersey.repackaged.org.objectweb.asm.ClassReader.(ClassReader.java:153) at jersey.repackaged.org.objectweb.asm.ClassReader.(ClassReader.java:424) at com.sun.jersey.spi.scanning.AnnotationScannerListener.onProcess(AnnotationScannerListener.java:138) at com.sun.jersey.core.spi.scanning.uri.FileSchemeScanner$1.f(FileSchemeScanner.java:86) at com.sun.jersey.core.util.Closing.f(Closing.java:71) at com.sun.jersey.core.spi.scanning.uri.FileSchemeScanner.scanDirectory(FileSchemeScanner.java:83) at com.sun.jersey.core.spi.scanning.uri.FileSchemeScanner.scanDirectory(FileSchemeScanner.java:80) at com.sun.jersey.core.spi.scanning.uri.FileSchemeScanner.scanDirectory(FileSchemeScanner.java:80) at com.sun.jersey.core.spi.scanning.uri.FileSchemeScanner.scan(FileSchemeScanner.java:71) at com.sun.jersey.core.spi.scanning.PackageNamesScanner.scan(PackageNamesScanner.java:226) at com.sun.jersey.core.spi.scanning.PackageNamesScanner.scan(PackageNamesScanner.java:142) at com.sun.jersey.api.core.ScanningResourceConfig.init(ScanningResourceConfig.java:80) at com.sun.jersey.api.core.PackagesResourceConfig.init(PackagesResourceConfig.java:104) at com.sun.jersey.api.core.PackagesResourceConfig.(PackagesResourceConfig.java:78) at com.sun.jersey.api.core.PackagesResourceConfig.(PackagesResourceConfig.java:89) at com.sun.jersey.spi.container.servlet.WebComponent.createResourceConfig(WebComponent.java:696) at com.sun.jersey.spi.container.servlet.WebComponent.createResourceConfig(WebComponent.java:674) at com.sun.jersey.spi.container.servlet.WebComponent.init(WebComponent.java:205) at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:394) at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:577) at javax.servlet.GenericServlet.init(GenericServlet.java:158) at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1132) at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1079) at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:973) at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4885) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5199) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1412) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1402) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:834) Any idea, what is wrong here?

                  - Subhash

                    JournalDev
                    DigitalOcean Employee
                    DigitalOcean Employee badge
                    February 19, 2019

                    I’m new to rest full and weblogic server. Can you please guide me how to deploy in weblogic 11gR1. I’m getting error 404,

                    - Ramakrishna

                      Try DigitalOcean for free

                      Click below to sign up and get $200 of credit to try our products over 60 days!

                      Sign up

                      Join the Tech Talk
                      Success! Thank you! Please check your email for further details.

                      Please complete your information!

                      Become a contributor for community

                      Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

                      DigitalOcean Documentation

                      Full documentation for every DigitalOcean product.

                      Resources for startups and SMBs

                      The Wave has everything you need to know about building a business, from raising funding to marketing your product.

                      Get our newsletter

                      Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

                      New accounts only. By submitting your email you agree to our Privacy Policy

                      The developer cloud

                      Scale up as you grow — whether you're running one virtual machine or ten thousand.

                      Get started for free

                      Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

                      *This promotional offer applies to new accounts only.