Java Webservice with Retrofit CURD with image uploading using Multipart

Java Web Service with Retrofit CRUD & Image Upload Using Multipart | Complete Tutorial

Java Web Service with Retrofit CRUD & Image Upload Using Multipart

Author: Deep Singh | Category: Java, Android, Web Services

Overview

This tutorial demonstrates how to build a Spring + Hibernate based Java Web Service that supports full CRUD operations and image uploading via Multipart. On the client side, we use Retrofit in Android to consume the REST API, including multipart image upload.

By the end, you'll know how to:

  • Set up a Spring MVC + Hibernate REST API
  • Configure multipart file upload on the server
  • Consume the API from Android using Retrofit
  • Upload images using MultipartBody.Part

Server-Side: Spring and Hibernate Based Web Service

Project Structure

Spring Hibernate Java web service project structure showing packages for config, controller, model, repo, and service
Figure 1: Spring + Hibernate web service project structure

AppConfig.java

package com.deepsingh44.config;

import java.util.Properties;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.deepsingh44.model.Quotes;

@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = { "com.deepsingh44" })
@PropertySource(value = "classpath:/application.properties")
public class AppConfig {

    @Autowired
    private Environment environment;

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.class"));
        dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
        dataSource.setUsername(environment.getRequiredProperty("jdbc.user"));
        dataSource.setPassword(environment.getRequiredProperty("jdbc.pass"));
        return dataSource;
    }

    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setAnnotatedClasses(Quotes.class);
        sessionFactory.setHibernateProperties(hibernateProperties());
        return sessionFactory;
    }

    private Properties hibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
        properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
        properties.put("hibernate.hbm2ddl.auto", environment.getRequiredProperty("hibernate.hbm2ddl.auto"));
        return properties;
    }

    @Bean
    public HibernateTransactionManager getTransactionManager() {
        HibernateTransactionManager transactionManager = new HibernateTransactionManager();
        transactionManager.setSessionFactory(sessionFactory().getObject());
        return transactionManager;
    }

    @Bean
    public CommonsMultipartResolver multipartResolver() {
        CommonsMultipartResolver cm = new CommonsMultipartResolver();
        cm.setMaxUploadSize(10 * 1024 * 1024);
        return cm;
    }
}

FrontController.java

package com.deepsingh44.controller;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.deepsingh44.config.AppConfig;

public class FrontController extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { AppConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

QuotesController.java

package com.deepsingh44.controller;

import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.deepsingh44.model.Quotes;
import com.deepsingh44.service.QuotesService;

@RestController
public class QuotesController {

    @Autowired
    private QuotesService quotesService;

    @PostMapping("/add")
    public Quotes addQuotes(@RequestBody Quotes quotes) {
        return quotesService.addQuotes(quotes);
    }

    @GetMapping("/quotes")
    public List<Quotes> getAllQuotes() {
        return quotesService.getAllQuotes();
    }

    @DeleteMapping("/quotes/{id}")
    public Quotes deleteQuotes(@PathVariable int id) {
        return quotesService.deleteQuotes(id);
    }

    @PutMapping("/update/{id}")
    public Quotes updateQuotes(@PathVariable int id, @RequestBody Quotes quotes) {
        return quotesService.updateQuotes(id, quotes);
    }

    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public String uploadFile(@RequestParam("file") MultipartFile file, HttpServletRequest req) throws IOException {
        String result = "Not Success";
        String root = req.getServletContext().getRealPath("/");
        File folder = new File(root, "images");
        if (!folder.exists()) folder.mkdir();
        File myfile = File.createTempFile("image", ".jpg", folder);
        file.transferTo(myfile);
        result = "Successfully Uploaded";
        return result;
    }
}

Quotes.java (Model)

package com.deepsingh44.model;

import javax.persistence.*;

@Entity
public class Quotes {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String title;
    private String description;

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }

    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }

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

QuotesRepo.java

package com.deepsingh44.repo;

import java.io.Serializable;
import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.deepsingh44.model.Quotes;

@Repository
public class QuotesRepo {

    @Autowired
    private SessionFactory sessionFactory;

    public Serializable addQuotes(Quotes quotes) {
        return sessionFactory.getCurrentSession().save(quotes);
    }

    @SuppressWarnings({ "unchecked", "deprecation" })
    public List<Quotes> getAllQuotes() {
        return sessionFactory.getCurrentSession().createCriteria(Quotes.class).list();
    }

    public Quotes deleteQuotes(int id) {
        Quotes quotes = getQuotesById(id);
        sessionFactory.getCurrentSession().delete(quotes);
        return quotes;
    }

    public Quotes getQuotesById(int id) {
        return sessionFactory.getCurrentSession().get(Quotes.class, id);
    }

    public Quotes updateQuotes(int id, Quotes quotes) {
        Quotes databasequotes = getQuotesById(id);
        if (databasequotes != null) {
            databasequotes.setTitle(quotes.getTitle());
            databasequotes.setDescription(quotes.getDescription());
            sessionFactory.getCurrentSession().update(databasequotes);
            return databasequotes;
        }
        return new Quotes();
    }
}

QuotesService.java

package com.deepsingh44.service;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.deepsingh44.model.Quotes;
import com.deepsingh44.repo.QuotesRepo;

@Service
public class QuotesService {

    @Autowired
    private QuotesRepo quotesRepo;

    @Transactional
    public Quotes addQuotes(Quotes quotes) {
        return ((Integer) (quotesRepo.addQuotes(quotes)) > 0) ? quotes : new Quotes();
    }

    @Transactional
    public List<Quotes> getAllQuotes() {
        return quotesRepo.getAllQuotes();
    }

    @Transactional
    public Quotes deleteQuotes(int id) {
        return quotesRepo.deleteQuotes(id);
    }

    @Transactional
    public Quotes updateQuotes(int id, Quotes quotes) {
        return quotesRepo.updateQuotes(id, quotes);
    }
}

application.properties

jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.class=com.mysql.jdbc.Driver
jdbc.pass=root
jdbc.user=root

hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.hbm2ddl.auto=update

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>Hello-Spring-Mvc</groupId>
    <artifactId>Hello-Spring-Mvc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>16</release>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.3</version>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.3.9</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.5.4.Final</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.3</version>
        </dependency>
    </dependencies>
</project>

Android Side: Retrofit Client

Project Structure

Android Retrofit project structure showing model, service, MainActivity, and SingleTask classes
Figure 2: Android Retrofit client project structure

Quotes.java (Android Model)

package com.example.restapiconsume.model;

public class Quotes {
    private int id;
    private String image;
    private String title;
    private String description;

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }

    public String getImage() { return image; }
    public void setImage(String image) { this.image = image; }

    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }

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

QuotesService.java (Retrofit Interface)

package com.example.restapiconsume.service;

import com.example.restapiconsume.model.Quotes;
import java.util.List;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.*;

public interface QuotesService {

    @GET("quotes/")
    Call<List<Quotes>> getQuotes();

    @POST("add/")
    Call<Quotes> addQuotes(@Body Quotes quotes);

    @PUT("update/{id}")
    Call<Quotes> updateQuotes(@Path("id") int id, @Body Quotes quotes);

    @DELETE("quotes/{id}")
    Call<Quotes> deleteQuotes(@Path("id") int id);

    @Multipart
    @POST("upload/")
    Call<String> uploadFile(@Part MultipartBody.Part file, @Part("file") RequestBody name);
}

MainActivity.java

package com.example.restapiconsume;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.loader.content.CursorLoader;

import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;

import com.example.restapiconsume.model.Quotes;
import com.example.restapiconsume.service.QuotesService;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.File;
import java.io.IOException;
import java.util.List;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class MainActivity extends AppCompatActivity {

    private EditText ttile, tdescription, tid;
    private QuotesService quotesService;
    private ImageView imageView, imagepicker;
    private MultipartBody.Part body;
    private RequestBody descBody;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SingleTask singleTask = (SingleTask) getApplication();
        quotesService = singleTask.getRetrofit().create(QuotesService.class);

        tid = findViewById(R.id.id);
        ttile = findViewById(R.id.title);
        tdescription = findViewById(R.id.description);
        imageView = findViewById(R.id.mainimage);
        imagepicker = findViewById(R.id.imagepicker);

        imagepicker.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setType("image/*");
                intent.setAction(Intent.ACTION_GET_CONTENT);
                startActivityForResult(Intent.createChooser(intent, "Select image to upload"), 1001);
            }
        });
    }

    private String getRealPathFromURI(Uri contentUri) {
        String[] proj = { MediaStore.Images.Media.DATA };
        CursorLoader loader = new CursorLoader(this, contentUri, proj, null, null, null);
        Cursor cursor = loader.loadInBackground();
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        String result = cursor.getString(column_index);
        cursor.close();
        return result;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1001 && resultCode == RESULT_OK) {
            Uri selectedImage = data.getData();
            try {
                Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), selectedImage);
                imageView.setImageBitmap(bitmap);

                File file = new File(getRealPathFromURI(selectedImage));
                RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
                body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
                descBody = RequestBody.create(MediaType.parse("application/json"), "this is image");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void addQuotes(View view) {
        Quotes quotes = new Quotes();
        quotes.setId(Integer.parseInt(tid.getText().toString()));
        quotes.setTitle(ttile.getText().toString());
        quotes.setDescription(tdescription.getText().toString());

        quotesService.addQuotes(quotes).enqueue(new Callback<Quotes>() {
            @Override
            public void onResponse(Call<Quotes> call, Response<Quotes> response) {
                Log.e("error", response.body().toString());
            }

            @Override
            public void onFailure(Call<Quotes> call, Throwable t) {
                Log.e("error", t.toString());
            }
        });
    }

    public void deleteQuotes(View view) {
        int id = Integer.parseInt(tid.getText().toString());
        quotesService.deleteQuotes(id).enqueue(new Callback<Quotes>() {
            @Override
            public void onResponse(Call<Quotes> call, Response<Quotes> response) {
                Log.e("error", response.body().toString());
            }

            @Override
            public void onFailure(Call<Quotes> call, Throwable t) {
                Log.e("error", t.toString());
            }
        });
    }

    public void updateQuotes(View view) {
        int id = Integer.parseInt(tid.getText().toString());
        Quotes quotes = new Quotes();
        quotes.setTitle(ttile.getText().toString());
        quotes.setDescription(tdescription.getText().toString());

        quotesService.updateQuotes(id, quotes).enqueue(new Callback<Quotes>() {
            @Override
            public void onResponse(Call<Quotes> call, Response<Quotes> response) {
                Log.e("error", response.body().toString());
            }

            @Override
            public void onFailure(Call<Quotes> call, Throwable t) {
                Log.e("error", t.toString());
            }
        });
    }

    public void allQuotes(View view) {
        quotesService.getQuotes().enqueue(new Callback<List<Quotes>>() {
            @Override
            public void onResponse(Call<List<Quotes>> call, Response<List<Quotes>> response) {
                Log.e("error", response.body().toString());
            }

            @Override
            public void onFailure(Call<List<Quotes>> call, Throwable t) {
                Log.e("error", t.toString());
            }
        });
    }

    public void uploadImage(View view) {
        Gson gson = new GsonBuilder().setLenient().create();
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(SingleTask.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

        QuotesService quotesService = retrofit.create(QuotesService.class);
        quotesService.uploadFile(body, descBody).enqueue(new Callback<String>() {
            @Override
            public void onResponse(Call<String> call, Response<String> response) {
                Log.e("error", new Gson().toJson(response.body()));
            }

            @Override
            public void onFailure(Call<String> call, Throwable t) {
                Log.e("error", t.toString());
            }
        });
    }
}

SingleTask.java

package com.example.restapiconsume;

import android.app.Application;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class SingleTask extends Application {

    private static Retrofit retrofit;
    public static final String BASE_URL = "http://192.168.1.26:9876/Hello-Spring-Mvc/";

    @Override
    public void onCreate() {
        super.onCreate();
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
    }

    public Retrofit getRetrofit() {
        return retrofit;
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:layout_height="wrap_content">

        <ImageView
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:id="@+id/mainimage"
            android:scaleType="centerCrop"
            android:src="@drawable/gallery"
            android:contentDescription="Selected image preview" />

        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_gravity="bottom|right"
            android:src="@drawable/gallery"
            android:id="@+id/imagepicker"
            android:contentDescription="Pick image from gallery" />
    </FrameLayout>

    <EditText
        android:id="@+id/id"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Id"
        android:padding="20dp"
        android:inputType="number" />

    <EditText
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Title"
        android:padding="20dp"
        android:inputType="text" />

    <EditText
        android:id="@+id/description"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Description"
        android:padding="20dp"
        android:inputType="text" />

    <Button
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:onClick="addQuotes"
        android:text="Add Quotes" />

    <Button
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:onClick="allQuotes"
        android:text="All Quotes" />

    <Button
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:onClick="deleteQuotes"
        android:text="Delete Quotes" />

    <Button
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:onClick="updateQuotes"
        android:text="Update Quotes" />

    <Button
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:onClick="uploadImage"
        android:text="Upload Image" />
</LinearLayout>

build.gradle (Module: app)

implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.google.code.gson:gson:2.8.7'

Android Permissions

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Output Screen

Android app output screen showing Quotes CRUD form with image picker and upload button
Figure 3: Android app output screen

No comments: